Implement Secure Magic-Link Login
Issue a single-use, short-lived, hashed token in an email link, verify it atomically, and bind it to the requesting context to prevent reuse.
Task
Add passwordless email login with single-use tokens that cannot be replayed.
When to use this
A magic-link token is stored in plaintext, never expires, and can be used repeatedly, so a leaked email grants permanent access.
Prerequisites
- An email delivery service
- A store for token hashes and expiry
Steps
1. Generate a high-entropy token, store only its hash with an expiry.
Expected output: A database leak does not reveal usable tokens.
2. Send a link containing the token over HTTPS with a short lifetime.
Expected output: The link is useless after the window.
3. On verification, atomically consume the token so it cannot be reused.
Expected output: Replay attempts fail.
4. Bind the token to a pending session or user and issue the real session only after success.
curl "https://app.example.com/auth/verify?token=$TOKEN"Expected output: Stolen links cannot be used elsewhere without context.
Troubleshooting
Problem
Link scanners consume the token
Solution
Some mail scanners follow links; consider a confirm button or a scanner-safe landing page.
Problem
Token reused
Solution
Consume atomically with a conditional update and reject consumed or expired tokens.
Limitations
- Magic links depend on email deliverability and can be delayed; they are not ideal for time-critical access.
- They shift the security boundary to the email account, so account takeover of email still compromises access.
Context
Add passwordless email login with single-use tokens that cannot be replayed. A magic-link token is stored in plaintext, never expires, and can be used repeatedly, so a leaked email grants permanent access.
Steps
- Generate a high-entropy token, store only its hash with an expiry. Expected: A database leak does not reveal usable tokens.
- Send a link containing the token over HTTPS with a short lifetime. Expected: The link is useless after the window.
- On verification, atomically consume the token so it cannot be reused. Expected: Replay attempts fail.
- Bind the token to a pending session or user and issue the real session only after success. Expected: Stolen links cannot be used elsewhere without context.
const token = base64url(crypto.randomBytes(32));
await db.insert({ hash: sha256(token), expiresAt: now + 10 * 60_000 });
// verify: atomic single use
await db.consume(hash, { now });
Verify
- A database leak does not reveal usable tokens.
- The link is useless after the window.
- Replay attempts fail.
- Stolen links cannot be used elsewhere without context.
Limits
- Magic links depend on email deliverability and can be delayed; they are not ideal for time-critical access.
- They shift the security boundary to the email account, so account takeover of email still compromises access.