What you'll learn
- Quick answer
- What a Magic Link Actually Is
- The Cryptography Underneath: Why Token Length Matters
- Single-Use Enforcement: The Part Teams Get Wrong
- Expiry Windows: The Real Tradeoff
- The New Single Point of Failure: Email Account Security
- Device/Session Binding and Its UX Cost
- When Magic Links Are the Right Call
- FAQ
Quick Answer
A magic link works by emailing a user a single-use, cryptographically random token embedded in a URL; clicking it proves they control that email account, and the server exchanges the token for a logged-in session. It removes password-reuse and credential-stuffing risk, but it also makes the user's email inbox itself the full authentication factor — if that inbox is compromised or the link is forwarded, so is the account.
What a Magic Link Actually Is
The flow looks simple from the outside: a user types their email, and a few seconds later an email arrives with a "Log in" button. Underneath, the server does four things. First, it generates a cryptographically random token — not a predictable value like an incrementing ID or a timestamp, a value with no pattern an attacker could guess or compute. Second, it stores that token (usually hashed, the same way you'd store a password) alongside the user's ID and an expiry timestamp. Third, it emails a URL containing the raw token to the address on file. Fourth, when that URL is visited, the server looks up the token, checks it exists, hasn't expired, and hasn't already been used, and if all three checks pass, it marks the token used and starts a normal logged-in session.
Notice what this flow never does: it never asks the user to remember or type a secret. The user's email account itself becomes the proof of identity — "I can receive mail at this address" stands in for "I know this password." That substitution is the entire security story of magic links, both the appeal and the risk, and it's worth keeping in view through everything below.
The Cryptography Underneath: Why Token Length Matters
The token itself has to be unguessable, which means it needs to come from a cryptographically secure random source and be long enough that brute-forcing or colliding with it is computationally meaningless. Python's secrets module is built for exactly this (never use the plain random module for anything security-sensitive — it's not cryptographically secure). A real token generated this way looks like:
import secrets
token = secrets.token_urlsafe(32)
# 8I1mfy8uQJ_wJcAekCANp28anEX0keGzR_GwrHB2KJoSecurity writeups on magic links commonly cite 128 bits of entropy as a safe floor. We computed what that actually buys you, rather than just asserting it's "enough." A 16-byte (128-bit) token has a total space of 2^128 ≈ 3.40 x 10^38 possible values. Using the birthday-bound approximation for collision probability (roughly N-squared divided by twice the space, for a space this much larger than N), we computed real numbers for a product issuing a lot of magic links over its lifetime:
- 10,000 tokens issued: collision probability ≈ 1.47 x 10^-31
- 100,000,000 tokens issued: collision probability ≈ 1.47 x 10^-23
- 1,000,000,000,000 (one trillion) tokens issued: collision probability ≈ 1.47 x 10^-15
All of those are so close to zero they're not a practical concern at any realistic product scale. For contrast, we ran the same math for a tempting-but-wrong 4-byte (32-bit) token, the kind that looks "random enough" sitting in a URL: its total space is only 4,294,967,296. After just 100,000 tokens issued, our computed collision exponent comes out around 1.16 — meaning a collision isn't just possible, the math says you should statistically expect one. That's the real, computed difference between "128 bits" as a number you read and "128 bits" as a reason your token is actually safe.
Single-Use Enforcement: The Part Teams Get Wrong
A magic link is only as single-use as its enforcement, and the naive implementation has a race condition. If your code reads "check the token is unused, then separately mark it used," two requests arriving close together (a user double-clicking, or a link scanner/prefetcher some email clients run automatically) can both pass the check before either one marks the token as spent — and now the single-use token has been used twice.
The fix that security writeups converge on is to make the check-and-mark a single atomic database operation rather than two separate steps: an UPDATE with a WHERE used_at IS NULL predicate, where you only proceed if exactly one row was actually affected by that update.
UPDATE magic_tokens
SET used_at = NOW()
WHERE token_hash = ? AND used_at IS NULL;
-- if rows_affected == 1: this request owns the login
-- if rows_affected == 0: someone already used this token, rejectThis collapses "is it unused" and "mark it used" into one database-level atomic step, so there's no window between the check and the write where a second request can sneak through. It's a small implementation detail, but it's the difference between a magic link that's genuinely single-use and one that only looks single-use in the happy path.
Expiry Windows: The Real Tradeoff
Most production magic link implementations expire tokens somewhere in the 5 to 30 minute range, and the reasoning is a direct tradeoff rather than an arbitrary number. A shorter window shrinks the amount of time an intercepted, forwarded, or leaked link stays exploitable — if the token is only good for five minutes, an attacker who gets hold of it an hour later has nothing. But users don't always click email links immediately; spam filters delay delivery, people open their inbox later, or the email ends up read on a different device than the one the login was requested from. A window too short turns into expired-link support tickets; a window too long turns into a longer-lived bearer credential sitting in an inbox. There's no single correct number — it's a product decision about which failure mode you'd rather have — but 5 to 15 minutes is the range that shows up most often in security-focused writeups as the sweet spot, with immediate single-use expiry (the link dies the instant it's clicked, regardless of the timer) layered on top regardless of the window chosen.
The New Single Point of Failure: Email Account Security
This is the tradeoff that doesn't make it into the marketing copy for passwordless auth. A magic link doesn't eliminate the "single secret controls the account" problem, it relocates it — from a password you set for this one app, to the inbox you use for everything. If an attacker already has access to that inbox (a reused password on the email provider itself, a compromised recovery phone number, a SIM-swap attack, a corporate account shared more widely than it should be), they don't need to attack your app's auth at all. They just request a magic link for your app and click it from the inbox they already control.
Two related risks follow directly from this. Forwarding: a user who forwards an email containing a live magic link — to themselves, to IT support, to a colleague helping them troubleshoot — has handed over account access along with it, often without realizing the link is still a live credential and not just an informational message. Adversary-in-the-middle phishing: a proxy sitting between the user and the real login page can capture a magic link token or the resulting session token in real time, the same way modern phishing kits already intercept OTP codes — magic links remove password-stuffing risk but don't remove real-time interception risk if the user is actively being phished at the moment they click.
Device/Session Binding and Its UX Cost
One mitigation is binding the token to something about the request context — the session cookie that was active when the link was requested, a coarse device fingerprint, or the requesting IP's rough network range — so that clicking the link from a completely different context raises suspicion or fails outright. This meaningfully reduces the value of an intercepted link to an attacker on a different network or device.
The catch is a very common, completely legitimate user flow: request the login link on a desktop browser, then open and click it from a phone's mail app, which is on a different IP, different device, and different session entirely. Binding too strictly breaks this constantly. The pragmatic middle ground that shows up in practice is a coarse binding (IP /24 subnet plus device class, not an exact match) combined with a fallback path — "this link looks like it's being used from somewhere different, confirm with a second factor" — rather than an outright hard reject.
When Magic Links Are the Right Call
None of this means magic links are a bad choice — it means they're the right choice for a specific risk profile. For a consumer product where email is already the de facto identity anchor (most users only ever reset a forgotten password by email anyway), magic links remove an entire class of password-reuse and credential-stuffing risk for a real usability win, and the residual risk is mostly bounded by how well the user secures their own inbox. For a high-value account — banking, admin panels, anything protecting money or infrastructure directly — relying on email possession as the only factor is a weaker guarantee than it looks, and the right move is to use a magic link (or password) as one factor and pair it with a second factor that doesn't live in the same inbox, such as an authenticator app or a hardware passkey.
