Magic links and email one-time codes hand the security of a customer account to whoever controls the customer's mailbox. They are popular in customer identity because they remove passwords, cut sign-up friction and work on any device with an inbox. They also inherit every weakness of email: compromised mailboxes, forwarding rules, shared inboxes, phishing sites that relay codes in real time, and corporate link scanners that click links before the user does. This guide explains how the two patterns work, what NIST SP 800-63B-4 and OWASP ASVS 5.0 actually say about email, how to design tokens, codes and flows that survive scanners and phishing, and when to move users on to passkeys. It is written for CIAM architects, application security engineers and product owners running consumer or business sign-in.
Key takeaways
- Email is a possession of the mailbox, not of the user. NIST SP 800-63B-4 says email shall not be used for out-of-band authentication, and OWASP ASVS 5.0 (V6.3.6, level 3) says email should not be a single or multi factor. Treat email sign-in as a low-assurance convenience method.
- Short, single-use, bound tokens are the baseline. Generate with a CSPRNG, store only a hash, expire within 10 minutes, invalidate on first use, and bind the token to the browser session that requested it.
- Link scanners will consume naive magic links. Secure email gateways fetch and detonate URLs before delivery, so a link that signs the user in on a plain GET request breaks for corporate users and can be abused. Require an explicit confirmation step or fall back to a code.
- Codes can be phished in real time; same-device links resist this better. A typed six-digit code can be relayed by a fake sign-in page. A magic link that only completes in the browser that started the request gives attackers far less to work with.
- Recovery is where email gets abused most. Changing the email address of record must require re-authentication and must notify the old address, otherwise every other control can be bypassed, as the Meta AI Instagram account takeovers showed.
How magic links and email OTP work
Both patterns prove that the person signing in can read messages sent to an email address already linked to the account. They differ in how the secret travels back to the service.
| Aspect | Magic link | Email OTP (code) |
|---|---|---|
| What the user does | Clicks a URL containing a random token | Types a short code (commonly 6 to 8 digits) into the page that requested it |
| Where the secret returns | Whatever browser opens the link, unless the service binds it to the requesting browser | The page where the user types it, which may be a phishing page |
| Cross-device use | Awkward: a link opened on a phone cannot easily complete a desktop session | Natural: read on the phone, type on the laptop |
| Effect of link scanners | Token can be consumed or flagged by pre-delivery URL scanning | Not affected; there is no URL to fetch |
| Real-time phishing relay | Harder when bound to the originating session | Easy: the attacker's page asks for the code and replays it |
| Entropy | High (128 bits or more is easy in a URL) | Low (a 6-digit code is about 20 bits), so rate limiting is essential |
| Typical use | Consumer sign-in, low-friction sign-up, account confirmation | Sign-in, step-up on low-risk actions, email verification, recovery |
Many platforms send both in the same message. Okta Identity Engine, for example, sends an email magic link and a one-time code together; the link only completes in the same browser on the same device, and otherwise the user returns to the original browser and types the code. The developer documentation explains that the link carries the code and a state token, and the receiving endpoint must match that state to the browser session before it completes sign-in.
See the Customer IAM (CIAM) Guide for where email sign-in sits among the other customer authentication options, and the Authenticator Apps and OTP Guide for app-generated codes.
What the standards say about email
Two sources settle most design arguments. Both are stricter than many product defaults.
| Source | Requirement | What it means in practice |
|---|---|---|
| NIST SP 800-63B-4, Sec. 3.1.3.1 | Email shall not be used for out-of-band authentication | An email code is not a recognised authenticator for assurance levels. Codes sent to validate an email address or issued as recovery codes are treated as not being authentication processes and are handled under the account recovery rules. |
| NIST SP 800-63B-4, Sec. 3.1.3.2 | Out-of-band secrets at least six decimal digits; authentication invalid unless completed within 10 minutes; rate limiting when the secret is under 64 bits; issuing a new secret does not reset the failure count | Good engineering targets for any emailed code, even though email itself is excluded |
| NIST SP 800-63B-4, Sec. 3.2.2 | No more than 100 consecutive failed attempts before the authenticator is disabled; agencies may set lower limits | An upper bound. Customer services should set a far lower limit per code and per account. |
| NIST SP 800-63B-4, Sec. 2.2.2 | AAL2 verifiers shall offer at least one phishing-resistant option | Email sign-in can be offered, but not as the only route for accounts that need AAL2 |
| OWASP ASVS 5.0 V6.3.6 (L3) | Email is not used as either a single-factor or multi-factor mechanism | High-value applications should not rely on email sign-in at all |
| OWASP ASVS 5.0 V6.5.1, V6.5.3, V6.5.4 | Out-of-band codes usable once; generated with a CSPRNG; at least 20 bits of entropy | Six random digits is the floor, not the target |
| OWASP ASVS 5.0 V6.5.5 | Out-of-band requests have a maximum lifetime of 10 minutes | Shorter than many defaults, some of which are an hour |
| OWASP ASVS 5.0 V6.6.2, V6.6.3 | Codes bound to the original request; brute force protected by rate limiting | A code issued for one sign-in attempt must not complete another |
| OWASP ASVS 5.0 V7.5.1, V6.3.7 | Full re-authentication before changing the email address; notify users after changes to authentication details | The recovery and change-of-email controls that matter most |
The practical reading: email sign-in is acceptable as a convenience method for low-risk consumer accounts, ideally as a bridge to passkeys, and as a component of account recovery with compensating controls. It is not acceptable as the only factor protecting payments, health data, administrative consoles or anything assessed at AAL2 or above.
Designing the token or code
- Generation: use the platform's CSPRNG (for example
secrets.token_urlsafe(32)in Python orcrypto.randomBytesin Node.js). For codes, draw uniformly from the digit space rather than truncating a timestamp or hash. - Storage: store a hash of the link token (SHA-256 is enough for a 256-bit random value) with the account ID, request ID, expiry and a used flag. A database read then never yields a working link.
- Lifetime: 10 minutes or less for sign-in, matching ASVS V6.5.5. Okta's email authenticator defaults to five minutes and recommends ten or less; Supabase defaults to one hour and strongly discourages anything over a day. Review the default in your platform rather than assuming it is safe.
- Single use: mark used atomically at redemption, and invalidate all outstanding tokens for the account when one succeeds or when a new one is requested.
- Binding: tie the token to the request that created it (ASVS V6.6.2). For magic links, set a random, HttpOnly, SameSite cookie or state value in the requesting browser and require it at redemption. Okta and Auth0 both enforce same-browser completion for this reason.
- Attempt limits: for codes, allow a small number of wrong guesses per code (three to five is common) and a per-account and per-IP budget across codes, so requesting a fresh code does not reset the counter (SP 800-63B-4 Sec. 3.1.3.2).
- Send rate: throttle how often a code or link can be requested per address. Supabase, for example, limits a user to one request every 60 seconds by default. This also limits email bombing of a victim.
- URL hygiene: build the link from a configured base URL, never from the request Host header, and serve the landing page with
Referrer-Policy: no-referrerso the token does not leak to third-party resources (OWASP Forgot Password Cheat Sheet). Strip the token from the address bar once it is redeemed. - Enumeration: return the same message and similar response time whether or not the address has an account. Auth0 notes that disabling sign-ups on passwordless connections can expose user enumeration, so test that behaviour.
Link scanners and prefetch breakage
Corporate email security tools open links before the recipient does. Microsoft Defender for Office 365 Safe Links scans URLs before delivery, can hold the message until scanning completes (the Wait for URL scanning to complete before delivering the message setting), and detonates URLs without a valid reputation in the background. Other gateways and some webmail providers behave similarly. Supabase documents the result directly: a confirmation URL prefetched by a provider's security feature is consumed instantly, and the user then sees "Token has expired or is invalid".
Two problems follow. First, legitimate business users cannot sign in, which pushes support teams to lengthen lifetimes or allow reuse. Second, if a GET request alone completes sign-in, whichever system fetched the URL first may hold the authenticated session. Same-browser binding prevents the second problem, but only if it is enforced on the server.
| Mitigation | How it works | Trade-off |
|---|---|---|
| Interstitial confirmation | The link opens a page with a button; the token is redeemed only by a POST from that page | One extra click; scanners that execute JavaScript and click buttons are rare but possible |
| Same-browser binding | Redemption requires the cookie or state set when the request was made | Breaks when users open mail on another device; offer a code fallback |
| Code instead of link | Send only a code, typed into the original page | Easier to phish in real time; lower entropy, so stricter rate limits |
| Link plus code in one message | Link for same-device users, code for everyone else | Two secrets to protect; both must be invalidated together |
| Scanner allow-listing | Ask enterprise customers to exclude your sign-in domain from rewriting (Safe Links "Do not rewrite the following URLs") | Depends on every customer's mail administrator; removes a protection they rely on |
Do not "fix" prefetch breakage by making tokens reusable for their lifetime or by accepting a token a second time from a different client. That converts a usability problem into a takeover path.
Threats and how to counter them
| Threat | ATT&CK reference | Control |
|---|---|---|
| Mailbox compromise gives the attacker every future sign-in link | T1586.002 Compromise Accounts: Email Accounts | Offer passkeys and nudge high-value users to enrol them; do not allow email alone to change other factors |
| Forwarding rule silently copies codes to the attacker | T1114.003 Email Forwarding Rule | Short lifetimes, same-browser binding, new-device notifications |
| Fake sign-in page relays the code in real time | T1566.002 Spearphishing Link | Put the requesting site, approximate location and device in the email; warn never to type the code elsewhere; prefer bound links or passkeys. See the AiTM Phishing Guide. |
| Code guessing across many requests | T1110 Brute Force | Per-code and per-account attempt budgets that survive reissue; bot management on the request endpoint |
| Session cookie stolen after sign-in | T1539 Steal Web Session Cookie | Session lifetime and re-authentication rules; device-bound sessions where supported. See the Device-Bound Session Credentials (DBSC) Guide. |
| Attacker adds their own email through support or self-service | T1078 Valid Accounts (the end state) | Re-authentication before change, notification to old address, cooling-off period for recovery-driven changes |
| Email bombing to bury a security alert or code | Not a single technique | Request throttling per address; see the Email Bombing and Help Desk Impersonation over Teams Guide |
A useful message design: state which site requested the code, the time, the approximate location and browser, the expiry time, and a single sentence telling the user to ignore the message if they did not request it. Never include account details beyond what is needed, and send from a domain with SPF, DKIM and DMARC enforced so spoofed sign-in mails are rejected. See the Email Authentication for Developers Guide.
Recovery, email changes and step-up
Email is most dangerous when it is the root of recovery for every other factor. If a user has a passkey and an email address, and the email address alone can register a new passkey, the account is only as strong as the mailbox. OWASP ASVS V6.4.3 asks that password reset does not bypass enabled multi-factor authentication, and V7.5.1 asks for full re-authentication before changing the email address or other recovery data.
- Change of email: require a current strong factor, send a confirmation link to the new address and a notice with a revert link to the old one, and delay the change for accounts with stored value or payment methods.
- Support-driven changes: the Meta AI Instagram account takeover shows the failure mode. An AI support assistant linked an attacker-supplied email address to accounts and sent the reset code there, affecting 20,225 people according to Meta's filing. Accounts with two-factor authentication were not taken over. Support channels, human or AI, must not be able to add or replace an email of record without the same proof the self-service flow demands. See the Account Recovery and Help Desk Security Guide.
- Step-up: an email code is a weak step-up for payments, payee changes or data export. Use a passkey or app-based approval, and record which method satisfied the step-up so audit can tell them apart.
- Account linking: when a social or enterprise login presents an email that matches an existing account, do not merge automatically on the strength of the email claim alone; require a sign-in to the existing account first.
Deciding when to use email sign-in
| Situation | Recommended approach |
|---|---|
| Low-risk consumer app (content, newsletters, community) | Magic link with interstitial and same-browser binding, code fallback, passkey offered after first sign-in |
| Retail or loyalty with stored value | Email sign-in only as a bridge; passkey or app approval for payments and profile changes; email changes delayed and notified |
| B2B SaaS sold to enterprises | Expect scanner breakage; prefer enterprise SSO per customer organisation, with email codes for small tenants. See the B2B CIAM and Organisation Management Guide. |
| Financial, health or regulated data | Do not use email as a factor (ASVS L3 V6.3.6); use it for notifications and recovery with additional checks |
| Administrative and staff consoles | Not appropriate; use phishing-resistant authentication |
| Small team with no identity engineers | Use the CIAM platform's built-in flow, then shorten the default lifetime, enable same-browser enforcement and turn on code fallback |
The long-term direction is to treat email sign-in as the on-ramp: verify the address, sign the user in once, then offer a passkey. See the Passkeys for Everyone Guide.
How to implement
- Inventory flows: list every place the service sends a link or code by email (sign-in, sign-up, verification, reset, change of email, invitations) and record lifetime, reuse, binding and rate limits for each.
- Set targets: adopt ASVS V6.5 and V6.6 as the specification: CSPRNG, hashed storage, 10-minute maximum, single use, bound to the request, rate limited.
- Change the redemption step: move redemption behind a POST from an interstitial page and add same-browser binding with a code fallback.
- Test with real scanners: send links to test mailboxes behind the main secure email gateways your customers use and confirm tokens survive delivery scanning and still fail when opened from another client.
- Pilot: release to a small percentage of sign-ins, watch completion rate and support tickets, and compare business domains with consumer webmail.
- Harden recovery: add re-authentication, old-address notification and a delay to email changes; audit support tooling for the same controls.
- Roll back safely: keep the old redemption path behind a feature flag for one release, but never re-enable token reuse; if completion collapses, widen the code fallback instead.
- Offer passkeys: prompt for a passkey after successful email sign-in and track enrolment.
Detection and measurement
- Log: request ID, account, IP, user agent and country at request and at redemption; redemption failures by reason (expired, used, binding mismatch, wrong code); email address changes and who initiated them.
- Alert: redemption from a different country or ASN than the request; many code requests for one address (bombing) or many addresses from one IP (enumeration or spraying); email change followed within minutes by a password or factor change.
| Metric | What it shows | Healthy signal |
|---|---|---|
| Sign-in completion rate, business domains vs webmail | Scanner breakage | Gap closes after interstitial rollout |
| Share of redemptions failing binding check | Cross-device use or relay attempts | Low and stable; spikes investigated |
| Median and maximum token age at redemption | Whether lifetimes are right-sized | Most within 2 minutes; none beyond policy |
| Wrong-code attempts per account per day | Guessing | Near zero outside attack windows |
| Email changes reverted by the old address | Takeover attempts caught | Every revert investigated |
| Passkey enrolment after email sign-in | Migration progress | Rising month on month |
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST CSF 2.0 | PR.AA-01, PR.AA-03 | Credential management and authentication of users |
| NIST SP 800-53 Rev. 5 | IA-8, IA-5, IA-11, AC-7, SC-23 | Non-organisational user authentication, authenticator management, re-authentication, failed attempt limits, session authenticity |
| OWASP ASVS 5.0 | V6.3.6, V6.4.3, V6.5.1 to V6.5.5, V6.6.2, V6.6.3, V7.5.1 | Testable requirements for codes, lifetimes, binding and recovery |
| ISO/IEC 27001:2022 Annex A | A.5.17 Authentication information, A.8.5 Secure authentication | Policy and technical control for issuing and handling secrets |
| CIS Controls v8.1 | Control 6 Access Control Management (6.3 MFA for externally exposed applications) | Email sign-in alone does not meet an MFA requirement |
Common failure patterns
- Hour-long, reusable links: lifetimes copied from defaults and reuse allowed to stop scanner complaints, giving anyone with mailbox access a long window.
- GET completes sign-in: the first client to fetch the URL, possibly a scanner or a proxy, gets the session.
- Counter reset on resend: each new code restarts the attempt budget, so guessing continues indefinitely.
- Host header in the link: reset or sign-in links built from the request Host header, letting an attacker point them at their own domain.
- Email as the root of every factor: an emailed link can register a new passkey or remove MFA, so phishing-resistant enrolment is undone by one mailbox compromise.
- Support bypass: human or AI agents can change the email of record without proof, as in the Meta AI Instagram case above.
Practitioner checklist
- List every emailed link and code and record its lifetime, reuse, binding and rate limit.
- Generate with a CSPRNG, store only hashes, expire within 10 minutes and invalidate on first use.
- Bind magic links to the requesting browser and redeem them only through a POST from an interstitial page.
- Offer a code fallback and keep attempt counters across reissued codes.
- Build links from configured base URLs and serve landing pages with Referrer-Policy no-referrer.
- Return identical responses for known and unknown addresses.
- Require re-authentication, notify the old address and delay changes to the email of record, including in support tooling.
- Do not use email as a factor for payments, admin access or AAL2 and above.
- Test delivery through the main secure email gateways before each change.
- Prompt for a passkey after email sign-in and measure enrolment.
Standards and references
- NIST: SP 800-63B-4 Authentication and Authenticator Management
- OWASP: Application Security Verification Standard 5.0
- OWASP: Forgot Password Cheat Sheet
- Microsoft Learn: Safe Links in Microsoft Defender for Office 365
- Okta Developer: Email Magic Links overview
- Okta: Configure the email authenticator
- Supabase: Email templates and email prefetching
- MITRE ATT&CK: Compromise Accounts: Email Accounts
Related NHI Mgmt Group resources: Customer IAM (CIAM) Guide · Passwordless and Passkeys Guide · Account Takeover Prevention Guide · Authenticator Apps and OTP Guide