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
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
expregardless 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.
Cookie storage vs localStorage
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:
| Token | Lifetime |
|---|---|
| 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-confirmation | 5–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
Auth0
Auth0 remains the safest mid-market default for B2C plus B2B Enterprise SSO when developer velocity matters more than long-run TCO. Below 50k MAU it is hard to beat. Above 500k MAU, cost and Actions-driven lock-in make alternatives like FusionAuth (self-host), Cognito (AWS-native), or Stytch plus Corbado (passkey-first) increasingly attractive.
Clerk
Clerk is the default for Next.js and React teams under 100k MAU who care about time-to-first-login and polished UI more than federation breadth. Above 100k MAU and into enterprise SSO breadth, Auth0 still leads. For passwordless and B2B Organizations under that ceiling, Clerk is among the strongest in the market.
Curity
Curity is the standards-purist enterprise CIAM in 2026, among the most spec-correct OAuth 2.0 / OIDC implementations available, with strong FAPI and Open Banking support that suits financial services and regulated workloads. The configuration-as-code model treats identity like infrastructure-as-code, which appeals to engineering-mature enterprises. Outside the standards-correctness or FAPI use cases, the enterprise pricing and learning curve make broader-scope CIAM (Auth0, Ping) more practical.
Stytch
Stytch is the strongest passkey-first CIAM in 2026 by orchestration quality, not raw feature count. Twilio acquired it on October 30, 2025; the product runs as a Twilio subsidiary with its own API surface, SDK family, and pricing, distinct from Twilio Verify. Post-acquisition the platform combines Stytch's modern auth with Twilio's communications infrastructure, repositioning it as a credible Auth0 alternative for developer-focused teams. Below 500k MAU the case is strong for both B2C and B2B SaaS; beyond that, gaps on FedRAMP, FGA, and adaptive MFA depth narrow it.
WorkOS
WorkOS is the strongest B2B-first CIAM in 2026 by deliberate scope choice, every product surface assumes the buyer is selling to enterprise IT, not to consumers. AuthKit's 1M MAU free tier makes it a credible Auth0 alternative for B2B SaaS that doesn't need adaptive risk or B2C consumer flows. For pure B2B SSO, SCIM, and audit logs, WorkOS is hard to beat at any price point.
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