Authentication Strategies: JWT vs. Session vs. Magic Links

Team 7 min read

#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

  • 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).
  • 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.
  • 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.