Authentication Strategies: JWT vs. Session vs. Magic Links
#authentication
#security
#webdev
#jwt
#sessions
#passwordless
Authentication Strategies: JWT vs. Session vs. Magic Links
Picking an authentication strategy affects your app’s security, performance, developer experience, and user experience. This guide compares three common patterns—cookie-based server sessions, JSON Web Tokens (JWTs), and passwordless magic links—so you can choose the right fit and implement it safely.
The quick take
-
Choose sessions when:
- You render primarily server-side, need straightforward revocation, and can maintain a shared session store.
- Security simplicity and immediate logout are priorities.
-
Choose JWTs when:
- You have multiple services or APIs and want stateless auth at the edge.
- You can handle token rotation, short expiries, and revocation lists.
-
Choose magic links when:
- You want low-friction logins and reduced password management overhead.
- Your use cases tolerate email/SMS delivery latency and you can harden against phishing.
You can also combine them: e.g., magic link for sign-in, then a secure session or JWT for ongoing access.
How each strategy works
1) Cookie-based server sessions
-
Flow:
- User authenticates once.
- Server creates a session record in a datastore (memory, Redis, database) and sets a session ID in an HttpOnly, Secure cookie.
- Each request includes the cookie; server looks up session data.
-
Pros:
- Simple to revoke and update server-side.
- Session invalidation is immediate.
- Works well with classic server-rendered apps and BFF patterns.
-
Cons:
- Requires shared session storage for horizontal scaling.
- CSRF protection is required for state-changing requests.
- Can be less convenient for non-browser clients unless you adopt token-based alternatives per client.
-
Key security choices:
- Cookies: HttpOnly, Secure, SameSite=Lax or Strict where possible.
- CSRF tokens or SameSite=strict plus double-submit cookie pattern.
- Short idle timeouts with sliding renewal if needed.
2) JWT (token-based, often “stateless”)
-
Flow:
- After a successful login, the server issues a signed access token (short-lived) and often a refresh token (longer-lived).
- Client presents the access token in Authorization header; server verifies signature locally without hitting a DB.
- Refresh token rotates to obtain new access tokens when access expires.
-
Pros:
- Good for microservices and edge deployments; verification is O(1).
- No centralized session lookup needed.
- Portable across services and clients.
-
Cons:
- Revocation is non-trivial; you need short expiries plus revocation lists or rotation with reuse detection.
- If stored unsafely (e.g., localStorage), tokens can be stolen via XSS.
- Complexity increases with rotation and key management.
-
Key security choices:
- Access tokens short-lived (5–15 minutes).
- Store refresh tokens in HttpOnly, Secure cookies; rotate on each use.
- Maintain a token revocation store or reuse-detection log.
- Validate iss, aud, exp, nbf, jti; pin algorithms; rotate signing keys (JWKS).
3) Magic links (passwordless)
-
Flow:
- User enters email or phone.
- Server generates a single-use, short-lived token and sends a link (email/SMS).
- User clicks the link; server verifies token and establishes a session or issues tokens.
-
Pros:
- No password to forget or breach.
- Great for consumer apps where friction reduction boosts conversion.
- Can pair with device-bound sessions for returning users.
-
Cons:
- Email/SMS delivery delays and spam filtering can frustrate.
- Link hijacking and phishing risks if URLs are leaked.
- Requires careful token scoping, single-use enforcement, and expiry.
-
Key security choices:
- Short expiry (e.g., 5–15 minutes) and single-use tokens with server-side invalidation.
- Bind token to anticipated client (ip, ua) if practical, and enforce HTTPS only.
- Clear, branded messaging and anti-phishing patterns; optionally require second factor on sensitive actions.
Security considerations that matter regardless of strategy
- Transport: Always use HTTPS; HSTS recommended.
- Least privilege: Scope tokens narrowly; avoid overbroad claims.
- XSS: Treat all user input and templates carefully; use Content Security Policy.
- CSRF: Cookies need CSRF defenses; tokens in Authorization header are typically safe from CSRF.
- Session fixation: Rotate session identifiers after authentication.
- Logout and revocation: Plan how you invalidate access quickly for high-risk accounts.
- Auditing: Log auth events (login, refresh, logout, failures) with correlation IDs.
Storage guidance
-
Cookies:
- Use HttpOnly and Secure for any credential-bearing cookie.
- Prefer SameSite=Lax or Strict; use CSRF tokens for cross-site POST if Lax is insufficient.
-
In-memory on the client:
- For SPAs using JWT access tokens, keep access token only in memory and refresh via HttpOnly cookie.
-
LocalStorage/sessionStorage:
- Avoid storing long-lived or high-privilege tokens; vulnerable to XSS exfiltration.
Performance and scalability
-
Sessions:
- Need a shared store (Redis, database). Latency depends on store performance.
- Straightforward cacheability at the page level may be trickier when sessions personalize responses.
-
JWTs:
- Verification is fast and cache-friendly at the edge.
- Perfect for distributed APIs; watch out for eventual consistency in revocation.
-
Magic links:
- Authentication step depends on email/SMS delivery; steady-state browsing is similar to sessions/JWTs once established.
Developer and user experience
-
Sessions:
- Easiest mental model for traditional web apps.
- Great for server-rendered flows, admin dashboards, and apps requiring immediate revocation.
-
JWTs:
- Works well across mobile, SPA, and server clients.
- More moving parts: rotation, revocation lists, key management.
-
Magic links:
- Frictionless onboarding; no password resets.
- Requires robust email deliverability and anti-abuse systems.
Choosing the right fit
-
Server-rendered apps, monoliths, internal tools:
- Start with sessions. Add JWTs later for API access if needed.
-
Public APIs, microservices, edge functions:
- JWTs with short-lived access tokens and rotating refresh tokens.
-
Consumer apps prioritizing convenience:
- Magic links for login, then issue a session or JWT for continuous access.
-
High-security or regulated environments:
- Sessions or short-lived JWTs with strict refresh, device binding, MFA, and comprehensive auditing.
-
Mobile and multi-client ecosystems:
- JWTs are versatile; pair with device-bound refresh tokens and native secure storage (Keychain/Keystore).
Implementation patterns
Secure server sessions (Node/Express example)
- Use a battle-tested session middleware with a hardened cookie:
- Cookie: HttpOnly, Secure, SameSite=Lax or Strict.
- Rotate session ID after login.
- Store session in Redis for scalability.
- Add CSRF protection for state-changing requests.
Pseudo-steps:
- On login: verify credentials, create session record { userId, roles, nonce }, set cookie.
- On each request: read cookie, load session, enforce authorization.
- On logout: destroy session in store, clear cookie.
JWT with refresh rotation (Node using jose)
- Access token: 5–15 minutes, sent via Authorization: Bearer.
- Refresh token: long-lived, HttpOnly+Secure cookie, rotated on every refresh.
- Maintain a refresh token store with status (active, used, revoked) and detect reuse.
Pseudo-steps:
- On login: issue access token (short), set refresh cookie (long). Store refresh token hash with userId and rotation counter.
- On refresh: verify cookie, ensure not previously used, rotate to a new token and invalidate the old one. Issue new access token.
- On logout: revoke refresh token in store and clear cookie.
Magic links
- Generate a signed, single-use token with short expiry.
- Store a hash of the token server-side with userId, expiry, and usage status.
- Email a link like: https://app.example.com/verify?token=…
- On click: validate token, check single-use and expiry, consume it, then create a session or issue JWTs.
- Consider linking to a deep link scheme for mobile apps and add friction for high-risk locations or devices.
Common pitfalls to avoid
- Storing JWTs in localStorage and suffering XSS token theft.
- Long-lived access tokens with no rotation or revocation plan.
- Missing CSRF protection on cookie-based sessions.
- Not rotating session IDs after login.
- Overloading JWTs with sensitive PII; prefer opaque IDs and fetch data server-side.
- Ignoring email deliverability and link expiration for magic links.
- Failing to verify iss/aud/alg on JWTs or to rotate signing keys.
A practical decision flow
- Need simple, secure, immediate revocation and mostly browser-based? Use sessions.
- Need cross-service auth at the edge with minimal shared state? Use JWTs with short lifetimes and rotation.
- Need frictionless onboarding and no passwords? Use magic links, then back with sessions or JWTs.
- Mixed needs? Combine: magic link for entry, session for web, JWT for API, with a unified logout and audit trail.
Final checklist
- HTTPS everywhere, secure cookies, CSP, and input sanitization.
- For sessions: CSRF tokens, session rotation, server-side invalidation.
- For JWTs: short-lived access, refresh token rotation, revocation, JWKS key rotation.
- For magic links: short expiry, single-use, branded messages, anti-phishing measures.
- Logging and alerting for auth events and anomalies.
Choose the simplest strategy that meets your security and scaling needs, and evolve it incrementally as your architecture grows.