JWT vs Session Cookies: Which Should Your App Actually Use?
· 6 min read
JWT got popular because it scales horizontally without a shared session store. That's the headline. But in practice, most teams that pick JWT default to it without considering the tradeoffs. Here's the version you can sketch on a whiteboard.
What each one actually is
Session cookie. The server stores session state (user ID, roles, etc.) keyed by a random session ID. The cookie just carries the ID. The server is the source of truth.
JWT. The server signs a payload containing the user's claims (id, roles, exp, etc.) with a private key. The signed token IS the credential. The server can verify it without looking anything up.
When sessions win
- You can revoke instantly. Delete the row in your session store and the user is logged out everywhere. JWT revocation requires a blocklist, which defeats the "no lookup needed" benefit.
- Your sessions are long-lived. A 30-day session in a JWT is a 30-day window where a stolen token grants full access. A 30-day session cookie can be invalidated server-side at any moment.
- You only have one backend. The horizontal-scaling argument doesn't apply.
When JWT wins
- Many independent services. Microservices that need to verify identity without calling back to an auth service benefit from self-contained tokens.
- Short-lived access tokens with refresh. Use a 5-minute JWT access token + a long-lived refresh token. Revocation is bounded by the access token TTL.
- Mobile and SPA clients on different domains. Cookies need careful CORS/SameSite config; bearer tokens in headers are simpler.
What to put in the payload
Never put secrets in a JWT — anyone holding the token can decode it. (Try our JWT Decoder.) Put only:
- `sub`: the user ID
- `exp`: expiration time
- `iat`: issued-at time
- minimal authorization claims (roles, tenant)
If the payload starts to grow, you're probably trying to avoid a database lookup that you should just do.
Decision framework
Pick sessions unless you have a specific reason to pick JWT. The default-to-JWT mindset has bitten many teams when they discovered they couldn't revoke tokens, couldn't change permissions mid-session, or shipped sensitive claims in the payload.
If you do pick JWT, keep access tokens short (5-15 min) and use refresh tokens for renewal. The combination gives you JWT's stateless verification with session-cookie-level revocation guarantees.