## TL;DR
A secure magic link is a single-use, high-entropy random token sent by email that expires in 10 to 15 minutes, is stored hashed (never plaintext) in your database, and dies the moment it is used. Generate the token with a secure random generator, store only its SHA-256 hash, email a link like https://example.com/login/verify?token [your value] and rate-limit requests per email address. Never make the link reusable or long-lived.

```text
how to build a secure magic-link login
```

## Use this when
- You want passwordless login for a web app or SaaS product
- A security review asks about your existing 'email me a login link' flow
- You are replacing passwords and comparing magic links vs passkeys
- You need to explain the threat model to a teammate

## Not for
- Social login via OAuth providers (different flow)
- Passkeys/WebAuthn (stronger option, separate skill)
- Getting your emails past spam filters (see the DMARC/SPF/DKIM skill)

## Steps
1. Generate the token with a secure random source. Create at least 32 random bytes and encode them URL-safe (base64url). This is the only secret in the flow; predictability here breaks everything.
   Expected output: every generated token is unique and unguessable; two requests never produce the same value.

2. Store only a hash, with expiry and single-use flags. Save SHA-256 of the token plus expires_at (now + 10-15 minutes), used_at (null), and the target user id. If your database leaks, attackers get hashes they cant turn back into login links.
   Expected output: the database row contains a hex hash, not the token from the email.

3. Email a link with the raw token in one query parameter. Keep the URL simple: https://example.com/login/verify?token [your value] Use your normal transactional email path, and make the email text say who requested it and that it expires soon, so forwarded or mistaken emails look suspicious to the recipient.
   Expected output: the email arrives with a clickable link, and the raw token appears nowhere else in your system.

4. Verify carefully on click. Look up the hash of the presented token, reject if missing, expired, or already used, then mark used_at immediately (before creating the session, to block double-click races). Bind the token to the user id from step 2, not to any id in the URL.
   Expected output: a fresh link logs the user in once; clicking it again shows 'link expired'.

5. Rotate the session after login. Create a brand-new session id at login (this also defeats session fixation) and set it as an httpOnly, Secure, SameSite=Lax cookie.
   Expected output: the session cookie value after magic-link login differs from any pre-login session.

6. Rate-limit and monitor. Cap link requests per email (e.g. 5 per hour) and per IP, log every issuance and redemption, and alert on redemption spikes or many requests for addresses that never click.
   Expected output: hammering the endpoint returns 429s, and the logs show a clean request-to-click trail.

## Variant phrasings
- "passwordless email link login best practices"
- "how long should magic link tokens last"
- "magic link vs OTP email code security"
- "implement email login link Node/Python"

## Edge cases and pitfalls
- Email is not instant: 10 minutes can be tight on slow providers, but going past 15-30 minutes meaningfully grows the window for inbox compromise. 15 is the sweet spot.
- Some email scanners pre-click links; single-use tokens mean the first click (the scanner) burns the users login. Mitigate with a short interstitial page requiring one more click ('Confirm sign-in'), which scanners wont press.
- Never put the users id or email in the URL alongside the token in a guessable way; the token alone should identify the login attempt.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_KG8w4Rc5HZAZuBVODVrXlw
