kavachOS
Authentication

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

Install

pnpm add kavachos

Add the plugin

lib/kavach.ts
import { createKavach } from 'kavachos';
import { magicLink } from 'kavachos/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({ 
      onSendLink: async (email, url) => { 
        await resend.emails.send({
          from: 'auth@example.com',
          to: email,
          subject: 'Your sign-in link',
          html: `<a href="${url}">Sign in to Example</a>. Link expires in 15 minutes.`,
        });
      }, 
    }), 
  ],
});

Handle the callback route

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

magicLink({
  redirectTo: '/dashboard', 
  onSendLink: async (email, url) => { /* ... */ },
}),

How it works

  1. User submits their email to POST /auth/magic-link/send.
  2. KavachOS generates a signed token and calls your onSendLink function with the email address and the full URL.
  3. User clicks the link in their inbox.
  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.

Endpoints

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. Attach a redirectTo in the body to override the default redirect for this request:

body: JSON.stringify({ email: 'user@example.com', redirectTo: '/onboarding' }),

Verify token

GET /auth/magic-link/verify?token=<token>

KavachOS handles this automatically when the user clicks the link. On success, the user is redirected. On failure (expired or already-used token), a 400 is returned.

Rate limiting

Requests to /auth/magic-link/send are limited to 5 per minute per IP address. Requests that exceed this return 429 Too Many Requests. Build a cooldown timer into your UI so users know when they can retry.

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

Options

OptionTypeDefaultDescription
onSendLink(email: string, url: string) => Promise<void>requiredCalled with the recipient email and the full magic link URL
tokenTtlnumber900Token lifetime in seconds (default: 15 minutes)
redirectTostring/Where to redirect after successful sign-in
createUserIfNotFoundbooleantrueAuto-create accounts for new emails

On this page