Magic Link Authentication
Passwordless authentication via email magic links.
Installation
The magic-link plugin is included with the auth plugin. Add it to your config:
typescript
// nevr.config.ts
import { defineConfig } from "nevr"
import { auth } from "nevr/plugins/auth"
import { magicLink } from "nevr/plugins/auth/magic-link"
export const config = defineConfig({
database: "sqlite",
entities: [],
plugins: [
auth({
plugins: [
magicLink({
sendMagicLink: async ({ email, url, token }) => {
await sendEmail(email, `Sign in: ${url}`)
},
}),
],
}),
],
})
export default configYour server picks it up automatically with nevr({ ...config, driver }).
Generate and push or migrate the database
bash
npx nevr generate # Generates user + session tables
npx nevr db:push # Push to database
# or
npx nevr db:migrate # Create migration filesClient Setup
typescript
import { createClient } from "nevr/client"
import { authClient } from "nevr/plugins/auth/client"
import { magicLinkClient } from "nevr/plugins/auth/magic-link/client"
import type { API } from "./api"
const client = createClient<API>()({
baseURL: "/api",
plugins: [authClient(), magicLinkClient()],
})Configuration
typescript
magicLink({
// Required: Send the magic link email
sendMagicLink: async ({ email, url, token }) => {
await emailService.send({
to: email,
subject: "Sign in to Your App",
html: `<a href="${url}">Click to sign in</a>`,
})
},
// Optional: Link expiration (default: 5 minutes)
expiresIn: 300,
// Optional: Auto-create user if not exists (default: true)
allowSignUp: true,
// Optional: Callback URL after verification
callbackURL: "/dashboard",
})Endpoints
Send Magic Link
POST /sign-in/magic-linkRequest:
json
{
"email": "user@example.com",
"callbackURL": "/dashboard"
}Response:
json
{
"success": true
}Verify Magic Link
GET /auth/magic-link/verify?token=xxx&callbackURL=/dashboardResponse: Redirects to callback URL with session cookie set.
Client Usage
typescript
import { createClient } from "nevr/client"
import { authClient } from "nevr/plugins/auth/client"
import { magicLinkClient } from "nevr/plugins/auth/magic-link/client"
import type { API } from "./api"
// Use curried pattern for full type inference
const client = createClient<API>()({
baseURL: "/api",
plugins: [authClient(), magicLinkClient()],
})
// sign in directly with magic link
await client.auth.signIn.magicLink({
email: "user@example.com",
callbackURL: "/dashboard",
})
// User clicks link in email -> automatically verifiedSecurity
- Tokens are single-use and expire after configured time
- Links are cryptographically signed
Rate Limiting
Built-in rate limiting protects against abuse. Fully configurable:
typescript
// Custom limits
magicLink({
sendMagicLink: async ({ email, url }) => {...},
rateLimit: { window: 60000, max: 3 }, // 3/min (stricter)
})
// Disable (use external limiter)
magicLink({
sendMagicLink: async ({ email, url }) => {...},
rateLimit: false,
})Default: 5 requests per 60 seconds
| Endpoints | Window | Max |
|---|---|---|
/sign-in/magic-link, /magic-link/verify | 60s | 5 |
