Skip to main content

Magic Link

Beta

This feature is in beta. Core behavior is stable and ready to try, but some APIs or configuration may still evolve before general availability.

Passwordless email login — users click a link to sign in, no password required.

Captcha Protection

When captcha is enabled, the Magic Link endpoint is automatically protected by Cloudflare Turnstile. All client SDKs handle token acquisition transparently — no code changes needed.

How It Works

  1. User enters their email address
  2. Server sends an email with a one-time magic link
  3. User clicks the link
  4. Your app extracts the token from the URL and calls verifyMagicLink
  5. User is signed in with full session tokens
Auto-Create

When autoCreate is enabled (default), users who don't have an account are automatically registered when they request a magic link. The created account has no password and is marked as verified.

Configuration

Enable magic link in your edgebase.config.ts:

export default {
auth: {
magicLink: {
enabled: true, // default: false
autoCreate: true, // auto-register unknown emails (default: true)
tokenTTL: '15m', // link expiration (default: '15m')
},
},
email: {
provider: 'resend',
apiKey: 'your-api-key',
from: 'noreply@yourapp.com',
// Optional fallback URL template ({token} placeholder)
// Per-request redirectUrl overrides this.
magicLinkUrl: 'https://yourapp.com/auth/magic?token={token}',
},
} satisfies EdgeBaseConfig;

Request a magic link email for a user:

await client.auth.signInWithMagicLink({
email: 'user@example.com',
redirectUrl: `${window.location.origin}/auth/magic`,
state: 'checkout',
});

The server always responds 200 OK regardless of whether the email exists. This prevents email enumeration attacks.

Per-Request Redirects

On the Web SDK, signInWithMagicLink() also accepts:

  • redirectUrl
  • state

If you pass them, EdgeBase uses that redirect for this request instead of the static email.magicLinkUrl template. The clicked link includes:

  • token
  • type=magic-link
  • state if provided

If your project sets auth.allowedRedirectUrls, the redirect must match that allowlist.

After the user clicks the link, extract the token from the URL and verify it:

// Extract token from URL (e.g., https://yourapp.com/auth/magic?token=abc123)
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
const state = params.get('state');

const { user, accessToken, refreshToken } = await client.auth.verifyMagicLink(token);
console.log('Signed in as:', user.email);
console.log('Resume flow:', state);

Full Example (React)

import { useEffect, useState } from 'react';
import { client } from './edgebase';

function MagicLinkLogin() {
const [email, setEmail] = useState('');
const [sent, setSent] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await client.auth.signInWithMagicLink({
email,
redirectUrl: `${window.location.origin}/auth/magic`,
state: 'dashboard',
});
setSent(true);
};

if (sent) {
return <p>Check your email for the sign-in link!</p>;
}

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
/>
<button type="submit">Send Magic Link</button>
</form>
);
}

// Callback page — handles the magic link redirect
function MagicLinkCallback() {
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
const state = params.get('state');
if (token) {
client.auth.verifyMagicLink(token).then(({ user }) => {
window.location.href = state === 'dashboard' ? '/dashboard' : '/';
});
}
}, []);

return <p>Signing you in...</p>;
}

Security

  • Single-use tokens — Each token can only be used once. After verification, the token is deleted.
  • Expiration — Tokens expire after tokenTTL (default 15 minutes).
  • No email enumeration — The server returns the same response whether or not the email exists.
  • Rate limiting — Auth rate limits apply to prevent abuse.
  • Auth hooksbeforeSignIn and afterSignIn hooks fire during magic link verification.
  • Redirect allowlist — If auth.allowedRedirectUrls is set, only approved redirect URLs can be used for per-request links.

Compatibility

Magic link works alongside other auth methods:

ScenarioBehavior
Email/password user requests magic linkWorks — signs in without password
Magic link auto-created user tries password sign-inFails — no password set (use magic link or set password via admin)
Magic link user links OAuth providerWorks — via account linking

REST API

POST /api/auth/signin/magic-link
Content-Type: application/json

{ "email": "user@example.com" }

Response: 200 OK

{ "ok": true }
POST /api/auth/verify-magic-link
Content-Type: application/json

{ "token": "abc123..." }

Response: 200 OK

{
"user": { "id": "...", "email": "user@example.com", "verified": true },
"accessToken": "eyJ...",
"refreshToken": "..."
}

Error Responses:

StatusCondition
400Missing or invalid email / missing token
400Token expired, invalid, or already used
403beforeSignIn hook rejected the sign-in
404Magic link authentication is not enabled