Overview

Use an opaque session ID in an HttpOnly, Secure cookie for browser users, with the session stored server-side. A long-lived JWT kept in localStorage cannot be revoked before expiry and is readable by any script that runs on the page. Keep JWTs for short-lived claims between services, as described in oauth-vs-jwt. Cookie flags, rotation, and CSRF rules live in auth-sessions.

Comparison

DimensionOpaque session cookieLong-lived JWT in localStorage
RevocationDelete the server record; takes effect on the next requestBlocklist (which adds state) or wait for expiry
Readable by page JavaScriptNo, with HttpOnlyYes
Server work per requestOne session lookupSignature verification
Permission changesApply on the next requestApply at the next refresh
CSRFNeeds SameSite and CSRF defensesNot from cookies; XSS is the main risk
Stateless verificationNoYes

Pick session cookies when

Pick server-side sessions for any user logged in through a browser.

  • Logout, password change, and “sign out everywhere” must work immediately.
  • HttpOnly stops a script from copying the credential, though XSS can still make authenticated requests while the page is open.
  • The cookie holds a random ID; roles and permissions are read fresh from the store.
  • Use SameSite=Lax or Strict, rotate the ID on login, set sliding and absolute expiry, and add CSRF tokens for state-changing requests.

Pick JWTs when

Pick JWTs when the verifier should not look up state.

  • Service-to-service calls inside a trust boundary, and claims forwarded by a gateway.
  • Short-lived access tokens from an OAuth authorization server (RFC 9068 defines a JWT profile for them).
  • Keep lifetimes to minutes so missing revocation matters less; validate signature, exp, aud, and iss, and pin the accepted algorithm.

Migrate

Introduce a session store (Redis; see redis-vs-memcached), issue random IDs as HttpOnly cookies, and add middleware that loads the session on each request. The change is small if the JWT already lived in a cookie and larger if client code read it from localStorage. Add CSRF protection when moving to cookies.