kavachOS
AuthenticationAuth methods

Magic link

Passwordless sign-in via a one-time email link.

Magic links let users sign in by clicking a link sent to their email. No password needed. The link is a signed, single-use token that expires after a configurable window.

Setup

Add the plugin

lib/kavach.ts
import { createKavach } from '@kavachos/core';
import { magicLink } from '@kavachos/core/plugins/magic-link';

const kavach = await createKavach({
  database: { provider: 'postgres', url: process.env.DATABASE_URL! },
  secret: process.env.KAVACH_SECRET!,
  baseUrl: 'https://auth.example.com',
  plugins: [
    magicLink({
      sendEmail: async ({ to, link }) => {
        await resend.emails.send({
          from: 'auth@example.com',
          to,
          subject: 'Your sign-in link',
          html: `<a href="${link}">Sign in to Example</a>. Link expires in 15 minutes.`,
        });
      },
    }),
  ],
});

Handle the callback route

Magic links redirect to baseUrl + /auth/magic-link/callback?token=.... KavachOS handles this automatically. You can set a redirectTo to control where users land after sign-in:

magicLink({
  redirectTo: '/dashboard',
  sendEmail: async ({ to, link }) => { /* ... */ },
}),

The sign-in flow

  1. User submits their email to POST /auth/magic-link/send.
  2. KavachOS generates a signed token and calls your sendEmail function.
  3. User clicks the link in their email.
  4. KavachOS validates the token, creates or retrieves the user, sets a session cookie, and redirects.

If the email belongs to an existing account, the same user ID is returned. If it is new, an account is created automatically.

POST /auth/magic-link/send

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

The response is always 200 to prevent email enumeration.

Options

OptionTypeDefaultDescription
sendEmailfunctionrequiredCalled with { to, link, token }
tokenTtlnumber900Token lifetime in seconds (default: 15 minutes)
redirectTostring/Where to redirect after successful sign-in
createUserIfNotFoundbooleantrueAuto-create accounts for new emails

Magic links are single-use. Clicking an expired or already-used link returns a 400. Show the user a "resend" option in your UI.

On this page