Passwordless Authenticationwith Magic Links
Magic links have moved from a niche pattern used by Slack and Medium into mainstream authentication. The premise is simple: instead of asking users to create and remember a password, you send them a link that proves they control the email address they registered with. Clicking the link proves identity. No password required.
But simple to use does not mean simple to implement securely. A poorly implemented magic link system can be more dangerous than a well-implemented password system. This guide covers how magic links work, what makes them secure, and the specific mistakes to avoid.
How Does a Magic Link Work?
The flow has four stages:
- Request: The user enters their email address and clicks 'Send magic link'. The server validates the email and generates a cryptographically random token using a CSPRNG (Cryptographically Secure Pseudo-Random Number Generator).
- Store: The server hashes the token (using a fast algorithm like SHA-256: bcrypt is unnecessary here since the token is already random and not user-chosen) and stores the hash alongside the user's email, an expiry timestamp, and a used flag.
- Send: The server emails the user a link containing the raw token as a query parameter: for example,
https://yourapp.com/auth/verify?token=abc123.... - Verify: When the user clicks the link, the server hashes the incoming token, looks up the stored hash, checks expiry, checks the used flag, and if everything matches, creates an authenticated session and marks the token as used.
The key insight is that the raw token exists only in two places: the email, and the user's browser when they click the link. The server never stores the raw token: only a hash. Even if the database is compromised, the attacker cannot use the stored hashes to authenticate.
Magic Links vs Passwords: Which Is More Secure?
The honest answer depends on your users. Magic links eliminate entire categories of password-related vulnerabilities, but they shift trust to the email channel.
| Security Dimension | Passwords | Magic Links |
|---|---|---|
| Credential theft via database breach | High risk: hashed passwords can be cracked | Low risk: tokens are random and single-use |
| Phishing attacks | High risk: users can be tricked into typing passwords | Medium risk: users can click malicious lookalike links |
| Credential stuffing | High risk: reused passwords are exploited at scale | Not applicable: no password to reuse |
| Weak credentials | High risk: users choose predictable passwords | Not applicable: no user-chosen secret |
| Brute force | Medium risk: rate limiting required | Very low risk: 128-bit random token space |
| Email account compromise | Not a factor | Critical risk: email access = full account access |
| No internet access at login time | Works fine | Fails: email must be accessible |
Magic links are strictly better for users who reuse passwords or choose weak ones: which is most users. They are worse for users whose email accounts are poorly secured. For most consumer applications, magic links represent a net security improvement.
Magic Links vs Passkeys: What's the Difference?
Passkeys (WebAuthn) are the more modern passwordless option, using public-key cryptography and device biometrics. The comparison matters for teams choosing between them.
| Dimension | Magic Links | Passkeys |
|---|---|---|
| Requires email access to authenticate | Yes, every time | No: uses device/biometric |
| Phishing resistance | Medium | Very high: bound to origin URL |
| Works on new devices | Yes: email accessible everywhere | Requires sync or fallback setup |
| Implementation complexity | Low | Higher: WebAuthn API required |
| User familiarity | High: everyone uses email | Growing: newer concept for users |
| Recovery path | Email is the recovery path | Needs backup codes or recovery method |
For most applications today, magic links are the pragmatic passwordless choice. Passkeys offer superior phishing resistance and are worth adding as an upgrade path: but magic links are simpler to implement, universally accessible, and already well-understood by users.
The Security Requirements a Magic Link Must Meet
OWASP's Forgot Password Cheat Sheet defines the security properties that any token-based authentication mechanism must satisfy. Applied to magic links:
- Cryptographic randomness: The token must be generated using a CSPRNG, not Math.random() or any predictable source. Minimum 128 bits of entropy: typically a 32-byte hex or base64url string.
- Hash before storing: The server stores the hash of the token, not the raw token. SHA-256 is sufficient because the token is already high-entropy and not user-chosen.
- Short expiry window: Tokens must expire. 15 minutes is the OWASP recommendation. Longer windows increase the exposure window if the email is intercepted.
- Single use enforced: The token must be invalidated immediately upon successful use. A token that can be reused is as dangerous as a password that never expires.
- Rate limiting on requests: Limit how many magic links a given email address can request per hour to prevent email flooding abuse.
- HTTPS only: The link must use HTTPS. Sending tokens over HTTP exposes them to interception.
How to Implement Magic Links in Node.js
You can implement magic links from scratch in Node.js, or use a library that handles the security requirements for you. The core implementation requires: a token generator, a storage mechanism (database), an email sender, and a verification endpoint.
The critical piece most implementations get wrong is token storage. Never store the raw token. Hash it first:
The token generation and verification flow in pseudocode: const token = crypto.randomBytes(32).toString('hex'); const hash = crypto.createHash('sha256').update(token).digest('hex');: store the hash, send the raw token in the email link, verify by hashing the incoming token and comparing to the stored hash.
OwlAuth handles all of these requirements automatically: CSPRNG token generation, SHA-256 hashing at rest, 15-minute expiry, and single-use enforcement: in a single sendMagicLink() call aligned with the OWASP Forgot Password Cheat Sheet.
Common Magic Link Mistakes to Avoid
- Using Math.random() for token generation: predictable, not cryptographic. Always use
crypto.randomBytes()in Node.js. - Storing the raw token in the database: if your database leaks, attackers get valid authentication tokens. Always store the hash.
- No expiry or very long expiry: a magic link valid for 24 hours gives attackers a full day to use an intercepted link. 15 minutes is the standard.
- Not enforcing single-use: if the token can be used multiple times, it functions like a persistent password that happens to live in an email.
- Sending to unverified email addresses: verify that the email address belongs to an existing account before sending a link, or you become a spam relay.
- No rate limiting: without limits, an attacker can flood any email address with magic link requests.