Skip to content

Magic Links

Workers Auth ships a complete magic link flow out of the box. Users sign in by clicking a one-time link sent to their email — no passwords required.

1. User submits email -> POST /auth/magic-link/send
2. Server generates token, stores in KV (10 min TTL)
3. Server sends email with verify link
4. User clicks link -> GET /auth/magic-link/verify?token=xxx
5. Server validates token, creates user (if new), creates session
6. Server sets session cookie and redirects to your app
import { MagicLinkStrategy } from 'workers-auth/authn/magic-link';
import { ResendProvider } from 'workers-auth/providers/resend';
import { defaultTemplate } from 'workers-auth/templates/default';
MagicLinkStrategy({
provider: ResendProvider({
apiKey: 'your-resend-api-key',
from: 'auth@yourdomain.com',
}),
template: defaultTemplate,
linkExpiry: 600, // seconds (default: 600 = 10 minutes)
});
OptionTypeDefaultDescription
providerEmailProviderrequiredEmail sending service (e.g. ResendProvider)
templateEmailTemplaterequiredFunction that returns { subject, html }
linkExpirynumber600Token TTL in seconds

A template receives { url, email, expiresIn } and returns { subject, html }:

import type { EmailTemplate } from 'workers-auth';
const myTemplate: EmailTemplate = ({ url, email, expiresIn }) => ({
subject: `Sign in to MyApp`,
html: `
<h1>Hello ${email}</h1>
<p>Click below to sign in. This link expires in ${expiresIn}.</p>
<a href="${url}">Sign in</a>
`,
});
MagicLinkStrategy({
provider: ResendProvider({ apiKey: '...', from: 'auth@example.com' }),
template: myTemplate,
});

POST the user’s email from your frontend:

await fetch('/auth/magic-link/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com' }),
});
// Returns { success: true }

Tokens are single-use. Once verified, the token is deleted from KV and cannot be reused.

Implement the EmailProvider interface to use any email service:

import type { EmailProvider } from 'workers-auth';
const myProvider: EmailProvider = {
async send({ to, subject, html }) {
await fetch('https://api.my-email-service.com/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to, subject, html }),
});
},
};