Design and implement magic-link email authentication with single-use tokens, safe redirects, request throttling, session controls, reliable delivery, and production-ready templates.
Magic link authentication lets a person request an email containing a time-limited URL, open it, and establish an application session without entering a password. It removes password handling from the normal login path, but it does not remove authentication risk: the mailbox becomes the credential channel and the link becomes a bearer secret.
A production design must prevent account enumeration, generate cryptographically random single-use tokens, bind each token to one purpose, restrict redirect destinations, consume the token atomically, and make email delivery observable. The button copy is the smallest part of the system.
How a magic link login works
1. Person submits normalized email address
2. Server returns the same neutral response for every address
3. Server rate-limits, creates random token, stores hash + purpose + expiry
4. Durable worker sends a login email to an eligible account
5. Browser opens HTTPS callback containing the token
6. Server hashes token and atomically marks matching unused record consumed
7. Server creates or elevates the intended session
8. Server redirects only to a pre-approved internal destination
9. System records security event and invalidates competing tokens by policyMagic link, one-time code, and password reset
METHOD USER ACTION IMPORTANT TRADEOFF
Magic link Open URL Fast; vulnerable to link scanners/prefetch
One-time code Copy/type code Works across devices; phishing still possible
Password reset Prove control, set password Changes credential; should not auto-login blindly
Passkey Device-backed ceremony Phishing-resistant when implemented correctly
Email proves access to a mailbox at that moment. It is not automatically strong
identity proof, transaction approval, or phishing-resistant MFA.Some email security products open links before the person does. A design that consumes the token on the first GET can therefore invalidate a legitimate login or let a scanner perform a state change. One mitigation is for the GET to validate the token and show a same-origin confirmation page, then consume it on an explicit POST. Assess the added friction against your threat model and email-client behavior.
Store a verifier, not the raw token
magic_link_id opaque record identifier
user_id nullable until account lookup policy permits
token_hash SHA-256 or keyed verifier of random token
purpose login | signup | reauth | invite
redirect_key pre-approved internal destination, not arbitrary URL
requested_at absolute server timestamp
expires_at short, documented lifetime
consumed_at null until successful atomic exchange
request_fingerprint privacy-reviewed abuse signal
email_message_id provider trace identifier
Unique constraint: one successful consume per magic_link_id/token_hash.Generate the token with a cryptographically secure random source and enough entropy to resist guessing. Store a hash or keyed verifier so a database read does not reveal live bearer links. OWASP's reset-token guidance calls for secure random generation, sufficient length, secure storage, single use, and an appropriate expiration; those properties are equally important when the token creates a login session.
Consume the token atomically
const verifier = sha256(token)
const link = await db.magicLinks.consumeIfValid({
verifier,
purpose: "login",
now: new Date(),
// UPDATE ... WHERE consumed_at IS NULL AND expires_at > now RETURNING ...
})
if (!link) return showInvalidOrExpired()
const session = await sessions.create({
userId: link.userId,
authMethod: "email_magic_link",
authenticatedAt: new Date(),
})
return redirect(allowedDestination(link.redirectKey))Do not read the row, create a session, and update consumed_at in three uncoordinated operations. Two concurrent requests can both pass the read. Use a transaction or conditional update so precisely one exchange wins. Decide whether requesting a newer link invalidates earlier links, and make the email explain that behavior.
Prevent account enumeration and inbox flooding
- Return the same message whether an account exists: for example, ‘If an eligible account exists, we sent a sign-in link.’
- Keep response timing reasonably uniform and move email submission off the request path.
- Rate-limit by normalized account identifier, IP or network signal, device/session signal, and system-wide budget.
- Do not let an attacker send thousands of messages to one address; add escalating challenges or cooldowns.
- Apply the same neutral behavior to suspended, SSO-only, deleted, and unverified accounts unless disclosure is intentional.
- Alert on unusual request, send, consume-failure, and cross-region patterns without logging raw tokens.
Lock down redirects and link construction
Build the origin from trusted server configuration, not an incoming Host or forwarded header unless a trusted proxy policy validates it. Accept a small redirect key such as billing or dashboard and map it to an internal path after authentication. An arbitrary next URL turns the login endpoint into an open redirect and makes a legitimate domain useful in phishing.
Use HTTPS, keep the token out of the URL after exchange, and set a Referrer-Policy that prevents it leaking to subresources. The landing page should contain no third-party scripts, pixels, chat widgets, or remote images before the token is removed. Scrub request logs and error reporting at the edge as well as in the application.
Magic link email template
Subject: Sign in to [Product]
Hi [First name or there],
Use this link to sign in to [Product]:
[Sign in to Product]
This link expires in [short lifetime] and can be used once. It was requested
for [masked address or workspace] at [time and timezone].
If you did not request this, you can ignore this email. No session will be
created unless the link is used. [Security/help route]
For your safety, support will never ask you to forward this email or send us
the sign-in link.The subject should name the action and product without exposing sensitive workspace details. Avoid ‘Verify your account’ when the action actually signs the reader in. Include the real lifetime, a recognizable sender, plain-text fallback, and a visible destination domain. Do not add promotional modules to an authentication secret.
Handle cross-device and same-browser constraints
People often request a link on a laptop and open it on a phone. A stateless bearer-link design can support that but creates the session on whichever device opens the email. A browser-bound transaction can reduce some interception risks but breaks that workflow; Auth0, for example, documents same-browser limitations for its Classic Login magic-link flow. Choose deliberately and explain the requirement before sending.
For higher-risk actions, use the link to return the user to a pending transaction and require an additional factor or explicit confirmation. Do not silently transfer a session, change an email address, pay money, or reveal sensitive data merely because a mail scanner or forwarded recipient opened a URL.
Make delivery part of authentication reliability
- Send from a dedicated, authenticated transactional stream protected from campaign spikes.
- Queue the message durably, but do not retry beyond the token's useful lifetime.
- Make duplicate jobs refer to one token/message intent rather than minting multiple valid links.
- Track accepted, delivered, delayed, bounced, and complained states without exposing the token.
- Offer a safe resend action that rotates or supersedes earlier links according to a documented policy.
- Give support the message ID, masked recipient, request time, and delivery state—not the bearer URL.
Test the failure cases
- Unknown and known accounts return indistinguishable request responses.
- Expired, malformed, wrong-purpose, already-used, and revoked tokens fail safely.
- Two simultaneous exchanges produce one session, not two successful consumes.
- External, encoded, protocol-relative, and nested redirect attempts cannot leave the allowlist.
- Link preview, antivirus, and corporate gateway requests do not create an unintended session.
- Provider timeout, delayed delivery, bounce, duplicate webhook, and resend are observable.
- Application, CDN, proxy, analytics, and exception logs contain no raw token.
Frequently asked questions
Are magic links secure?
They can be appropriate when mailbox control matches the application's assurance needs and the implementation uses strong, short-lived, single-use tokens with safe exchange and session controls. They inherit the risks of compromised mailboxes, forwarding, phishing, link scanners, and account recovery. High-risk applications may need passkeys or another factor.
How long should a magic link last?
Use the shortest lifetime that remains practical for your delivery conditions and users, commonly measured in minutes rather than days. There is no universal number. Observe delivery delays, state the actual expiry in the email, and require a fresh request after expiration.
Should opening a magic link log the user in immediately?
That is convenient but can interact badly with security scanners that prefetch links. For sensitive systems, validate on GET and require a same-origin confirmation POST before consuming the token. Whatever pattern you choose, do not perform unrelated high-impact actions on an unauthenticated GET.