Magic Link Login
Passwordless authentication via one-time email links.
All endpoints require Authorization: Bearer <jwt> unless noted. Token TTL: 15 minutes (non-renewable; request a new link after expiry).
Endpoints
POST /api/auth/magic-link/send
Sends a magic link to the provided email address.
Input: ~~~json { "email": "user@example.com", "application": "myapp" } ~~~
Output: ~~~json { "message": "Magic link sent successfully" } ~~~
GET /api/auth/magic-link/verify?token=:token
Verifies a magic link token and issues a session JWT.
Output: ~~~json { "access_token": "eyJ0...", "user": { "id": "user-uuid-123", "email": "user@example.com" } } ~~~
Tokens are single-use; attempting to verify an already-consumed token returns 400 already_used.
Security Notes
- Links expire after 15 minutes.
- Each link is single-use.
- Rate limited to 5 requests per email per hour.
- The token is signed with the application's signing key.
- B2C sign-in where you want to reduce friction
- B2B sign-in where SSO isn't yet configured but you want better UX than passwords
SDK Examples
Python
~~~python client.magic_link_send("user@example.com", redirect_url="https://app.example.com") resp = client.magic_link_verify("token-from-email") ~~~
Node.js / TypeScript
~~~typescript await client.magicLinkSend('user@example.com', { redirectUrl: 'https://app.example.com' }); const resp = await client.magicLinkVerify('token-from-email'); ~~~
Go
~~~go _, err := client.SendMagicLink(ctx, "user@example.com", nil) resp, err := client.VerifyMagicLink(ctx, "token-from-email") ~~~
Rust
~~~rust client.magic_link_send("user@example.com", Some("https://app.example.com")).await?; let login = client.magic_link_verify("token-from-email").await?; ~~~
When to Use
Error Reference
| Status | Code | Meaning |
|--------|------|---------|
| 400 | invalid_token | Magic link token is malformed |
| 400 | expired_token | Magic link has expired (15 min TTL) |
| 400 | already_used | Magic link has already been consumed |
| 401 | unauthorized | Token verification failed |
| 404 | user_not_found | No account for the email in the link |
| 429 | rate_limited | Too many magic link requests for this email |