Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

JWT vs session cookies

On this page
  1. How they differ
  2. Where session cookies win
  3. Where JWTs win
  4. Where JWTs hurt
  5. The hybrid pattern (what production typically does)
  6. Mobile and native clients
  7. Decision flowchart
  8. Common mistakes
  9. Try the tools
  10. Related across the network

TL;DR. For most web applications, session cookies are the safer default. Use JWTs when you have a real reason: cross-service auth, stateless APIs, or systems where database-lookup-per-request is a measured bottleneck. The hybrid pattern (HttpOnly cookie containing a JWT) gets you most of both worlds.

How they differ

Session cookieJWT (typical)
Stored where?Server-side DB or cacheThe token itself carries data
Cookie or header?CookieEither; commonly Authorization: Bearer
Per-request lookupDB readJust signature verification
Size~32-byte ID~300–800 bytes
RevocationDelete from DB → invalidHard — see below
Cross-service authNeeds shared session storeVerify with public key, done
XSS exposureNone (HttpOnly cookie)High (often in localStorage)
Library maturityMature, boringMature, has had famous bugs

Where session cookies win

Where JWTs win

Where JWTs hurt

The hybrid pattern (what production typically does)

The cleanest pattern for a typical web app:

  1. Cookie-based session at the auth layer. When the user logs in, the auth service issues a session cookie (HttpOnly, Secure, SameSite=Lax) to the browser. The cookie is tied to a row in a sessions table.
  2. The browser sends the cookie automatically with every same-site request.
  3. Internal RPC between services uses short-lived JWTs minted from the active session, signed by the auth service.
  4. Other services verify the JWT with the auth service’s public key — no session-store lookup required.

This way:

The user’s browser never sees a JWT. JWTs live entirely in service-to-service communication.

Mobile and native clients

For non-browser clients, the calculus shifts:

A common pattern: opaque refresh token in keychain, short-lived JWT access token in memory only. The JWT gets refreshed when it expires; the refresh token rotates with each use.

Decision flowchart

Are you building a single-server / single-service web app?
└── Session cookies (simpler, safer)

Do you have many backend services that need to authenticate users?
└── JWT for service-to-service; cookie at the browser edge

Are you building a mobile / native / CLI app?
└── JWT (cookies are awkward)

Are you consuming OIDC / OAuth?
└── You're getting JWTs — verify them properly

Do you need easy logout / kick-everywhere?
├── Cookies → trivial
└── JWTs → short expiry + refresh tokens, plus blocklist

Is per-request DB lookup a measured bottleneck?
├── Yes → JWT
└── No → cookies (this is rarely the bottleneck for normal apps)

Common mistakes

Try the tools