Remix Auth Email Link Strategy
remix-auth-email-link provides a passwordless authentication strategy for Remix applications, leveraging 'magic links' sent via email. It is heavily inspired by the kcd strategy from Remix Auth v2. The library is actively maintained, with frequent patch and minor releases, and its current stable version is 2.1.1. A significant differentiator is its use of `crypto-js` instead of Node's built-in `crypto` module, enabling deployment on various serverless runtimes like Cloudflare Workers. It integrates with the `remix-auth` ecosystem and requires a custom `sendEmail` function to interface with any email service (e.g., Mailgun, SendGrid, AWS SES). This strategy streamlines user login by eliminating password management, offering a user-friendly and secure authentication flow.
Common errors
-
Error: Missing MAGIC_LINK_SECRET env variable.
cause The `MAGIC_LINK_SECRET` environment variable is not set, which is required by the `EmailLinkStrategy` for token encryption.fixSet the `MAGIC_LINK_SECRET` environment variable in your development and production environments. For example, in a `.env` file: `MAGIC_LINK_SECRET="a_very_secure_random_string"`. -
TypeError: (0 , import_remix_auth_email_link.EmailLinkStrategy) is not a constructor
cause This error typically occurs in CommonJS environments when trying to import an ESM-only package or when mixing import/require styles incorrectly, especially in older Remix setups or certain bundler configurations.fixEnsure your Remix project and build setup are configured for ESM. If using TypeScript, check `tsconfig.json` for `"module": "esnext"` or `"module": "node16"`. If still encountering issues, verify that `remix.config.js` is correctly configured for your target environment (e.g., Cloudflare, Node). -
Error: Response not handled by strategy (e.g. redirect or body).
cause This error usually indicates that the `authenticator.authenticate` call in your loader/action did not result in a redirect or other handled response, often due to an issue within the strategy's verify callback or options.fixCheck the `successRedirect` and `failureRedirect` options passed to `authenticator.authenticate`. Ensure your `EmailLinkStrategy`'s `verify` callback correctly returns a user object or throws an error as expected. Also, confirm the `callbackURL` matches the actual route.
Warnings
- breaking Version 2.0.0 introduced compatibility with Remix v2. If you are upgrading your Remix application to v2, you must also upgrade `remix-auth-email-link` to v2.x to maintain compatibility.
- gotcha The strategy relies on a `MAGIC_LINK_SECRET` environment variable for encrypting tokens. Failing to provide this secret will result in a runtime error during initialization.
- gotcha The `callbackURL` option provided to `EmailLinkStrategy` must exactly match the URL of the route where you call `authenticator.authenticate('email-link', request, { ... })` for processing the magic link.
- gotcha When running locally, older versions of the package (prior to v2.0.2) might incorrectly assume `https` for magic links. This can cause issues in development environments where `http` is typically used.
Install
-
npm install remix-auth-email-link -
yarn add remix-auth-email-link -
pnpm add remix-auth-email-link
Imports
- EmailLinkStrategy
const { EmailLinkStrategy } = require('remix-auth-email-link')import { EmailLinkStrategy } from 'remix-auth-email-link' - SendEmailFunction
import type { SendEmailFunction } from 'remix-auth-email-link' - Authenticator
import { Authenticator } from 'remix-auth-email-link'import { Authenticator } from 'remix-auth'
Quickstart
import { Authenticator } from 'remix-auth';
import { EmailLinkStrategy } from 'remix-auth-email-link';
import { createCookieSessionStorage, redirect } from '@remix-run/node';
// Minimal session storage for demonstration
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
path: '/',
sameSite: 'lax',
secrets: [process.env.SESSION_SECRET ?? 's3cr3t'],
secure: process.env.NODE_ENV === 'production',
},
});
// Mock user model and email service for example
interface User { id: string; email: string; name: string; }
const users: User[] = [
{ id: '1', email: 'user@example.com', name: 'Test User' }
];
const getUserByEmail = (email: string) => users.find(u => u.email === email);
const createUser = (email: string) => {
const newUser = { id: String(users.length + 1), email, name: 'New User' };
users.push(newUser);
return newUser;
};
const sendEmail = async ({ emailAddress, magicLink, user, domainUrl, form }) => {
console.log(`Sending magic link to ${emailAddress}: ${magicLink}`);
// In a real app, integrate with an actual email service like Mailgun/SendGrid/SES
// e.g., await emailProvider.sendEmail(emailAddress, 'Login Link', `<a href="${magicLink}">Click to login</a>`);
};
const secret = process.env.MAGIC_LINK_SECRET ?? 'another_s3cr3t';
if (!secret) throw new Error('Missing MAGIC_LINK_SECRET env variable.');
export const authenticator = new Authenticator<User>(sessionStorage);
authenticator.use(
new EmailLinkStrategy(
{ sendEmail, secret, callbackURL: '/magic' },
async ({ email, form, request, magicLink, user }) => {
let existingUser = getUserByEmail(email);
if (!existingUser) {
// Create user if they don't exist
existingUser = createUser(email);
}
// Log the user in
return existingUser;
}
),
'email-link'
);
// Example Remix loader for a magic link callback route (/magic)
export const loader = async ({ request }) => {
return await authenticator.authenticate('email-link', request, {
successRedirect: '/dashboard',
failureRedirect: '/login',
});
};
// Example Remix action for a login form
export const action = async ({ request }) => {
return await authenticator.authenticate('email-link', request, {
successRedirect: '/login?checkEmail=true', // Redirect to a page asking user to check email
failureRedirect: '/login',
});
};
console.log('Authenticator and strategy initialized.');