Skip to content
architecture

Session Management: JWTs vs Opaque Tokens, and How to Pick

Updated 2026-05-07 · 12 min read · By @guptadeepak

Key takeaways

  • JWT sessions scale horizontally without server state but are hard to revoke before expiry, the central trade-off.
  • Opaque tokens (server-side sessions) are immediately revocable but require a session-store lookup on every request.
  • The 2026 default for production B2B SaaS is hybrid: short-lived JWT access tokens (5–15 min) plus rotating opaque refresh tokens with server-side revocation.
  • Refresh token rotation with reuse detection is required by OAuth 2.1 and catches token theft via the replay signal.
  • Cookies > localStorage for browser session storage. HttpOnly + Secure + SameSite=Lax (or Strict) is the floor.

What's actually at stake

JWT is verified locally with no DB round-trip; opaque tokens require a session store lookup but allow instant revocation. The 2026 hybrid uses a short-lived JWT for the API path plus a server session for revocation.
JWT is verified locally with no DB round-trip; opaque tokens require a session store lookup but allow instant revocation. The 2026 hybrid uses a short-lived JWT for the API path plus a server session for revocation.

The classic mistake is picking JWT for "scale" reasons and discovering months later that revocation doesn't work the way the team assumed. The classic mistake on the other side is picking opaque tokens and discovering the session-store lookup is now a single point of failure for every authenticated request.

JWT sessions

A JWT (JSON Web Token) is a self-contained signed payload. The server validates the signature with the issuer's public key and trusts the claims (sub, iss, aud, exp, plus app-specific data) without any database lookup.

Wins:

  • Stateless, any server can validate any JWT without coordinating.
  • Fast, signature verification is cheap, no network round-trip.
  • Works across services, services consume JWTs from a shared issuer without sharing a session store.

Losses:

  • Hard to revoke. The token is valid until exp regardless of whether the user logged out, the account was deleted, or the device was lost. Workarounds (denylists, very short lifetimes) defeat or limit the no-state property.
  • Larger payload, JWT carries the claims; opaque tokens are just an opaque string.
  • Algorithm-substitution attacks if validation isn't pinned to a specific algorithm. (Pin RS256 or ES256 explicitly.)

Opaque tokens (server-side sessions)

An opaque token is a random string. The server holds the authoritative session record in a database (Postgres, Redis, dedicated session store). Each request looks up the session, validates it, and applies the user's identity to the request.

Wins:

  • Immediate revocation, delete the session row, the token stops working on the next request.
  • Smaller token, just a random string.
  • Centralized session control, admin tools can list, terminate, and audit sessions per-user.

Losses:

  • Per-request store lookup, adds latency and creates a single point of failure.
  • Doesn't compose across services without a shared session store or a token-introspection endpoint.
  • Operational overhead of running the session store at scale.

The 2026 hybrid default

Most production B2B SaaS in 2026 runs a hybrid:

  • Short-lived JWT access token (5–15 minutes). Validated locally on every request, fast, stateless. The short lifetime bounds the blast radius of token theft.
  • Long-lived opaque refresh token (days to weeks). Stored server-side in the session table. Used to mint a new access token when the old one expires. Rotated on each use; reuse-detection revokes the entire chain.

The composition gives both properties:

  • Fast request handling, most requests carry the JWT and validate locally.
  • Revocation works, invalidate the refresh token and the user is logged out within the access-token TTL (5–15 min worst case).
  • Theft detection, refresh token rotation with reuse detection catches token replay even when the user hasn't noticed compromise yet.

OAuth 2.1 requires refresh token rotation for non-confidential clients. Most modern CIAM ship this default-on.

For browser-based sessions, the storage choice is between:

  • Cookies with HttpOnly, Secure, SameSite=Lax (or Strict). The browser attaches the cookie automatically; the cookie is unreadable by JavaScript; HTTPS-only transport.
  • localStorage / sessionStorage. JavaScript reads the token explicitly and adds it to request headers. Convenient for SPAs; readable by any JavaScript on the page including XSS-injected scripts.

Cookies are the right default in 2026. The classic SPA argument for localStorage was CSRF protection, but SameSite cookies plus CSRF tokens handle that without exposing the token to XSS. Multiple production token-leak incidents (browser extensions, third-party scripts, dependency-confusion attacks injecting JS) have specifically exploited localStorage-stored tokens.

Token lifetime guidance

The 2026 production defaults:

TokenLifetime
Access token (JWT)5–15 minutes
Refresh token (opaque, rotating)7–30 days
ID token (JWT)Same as access token
Long-lived API token (machine-to-machine)90 days, scoped, rotatable
Step-up auth re-confirmation5–15 minutes for sensitive actions

Variations: long-lived sessions (one year) for low-risk consumer apps where re-auth friction would hurt retention; short-lived sessions (5 minutes) for the highest-risk surfaces (financial transfers, admin actions on production data).

What CIAM vendors handle for you

Most modern CIAM ship the hybrid model out of the box:

  • Auth0, refresh token rotation default-on; configurable token lifetimes per Application.
  • Stytch, server-side session model with refresh tokens; explicit support for the hybrid pattern.
  • Clerk, short-lived JWT plus refresh; cookies as the default storage.
  • WorkOS, JWT access tokens plus session-state lookup for revocation.
  • Curity, standards-purist OAuth 2.1 with full hybrid support and token introspection endpoints.

Lighter CIAM and OSS platforms vary. Keycloak and FusionAuth ship the model competently. SuperTokens is built around session management as the design center, strong primitives if session control is a binding constraint.

For deeper guidance on token validation, see the OAuth/OIDC client config best-practice and the OAuth 2.1 explained guide.

Related vendors

FAQ

Can I revoke a JWT before it expires?
Not without a server-side check. JWTs are self-contained, the server validates the signature and trusts the claims, with no per-request lookup. Revocation requires either a denylist (defeats the no-state property), short token lifetime (limits the blast radius without revoking), or pairing JWT access tokens with opaque refresh tokens that have server-side revocation.
Should I store JWTs in localStorage or cookies?
Cookies, with HttpOnly, Secure, and SameSite=Lax (or Strict for the most-sensitive surfaces). localStorage is readable by any JavaScript on the page, including XSS-injected scripts. The cookie pattern accepts a small CSRF surface mitigated by SameSite plus CSRF tokens; it eliminates the XSS-amplifier risk localStorage carries.
What is refresh token rotation?
Each refresh issues a new refresh token; the old one is revoked. Detecting reuse of the old token is a signal of replay attack, the server revokes the entire token chain. OAuth 2.1 requires this for non-confidential clients. Most modern CIAM ship rotation default-on.
What's an opaque token?
An opaque token carries no claims itself, it's just a random string the server looks up in a session store to find the associated session record. The server-side store is the source of truth; revoking is a database row update. Opaque tokens are slower per request (one extra lookup) but trivially revocable.

Sources

  • OAuth 2.1 specification
  • OWASP Session Management Cheat Sheet
  • OAuth 2.0 Security Best Current Practice
Last reviewed 2026-05-07.