Where the state actually lives
Session-based auth: the server generates a session ID, stores the actual user/session data server-side (in memory, a database, or a cache like Redis), and sends only the ID to the client as a cookie. Every request, the server looks up that ID to retrieve the real session state.
JWT-based auth: the server encodes the session data directly into a signed token and hands the whole thing to the client. Every request, the client sends the token back, and the server verifies the signature — no lookup against server-side storage needed, because the data travels with the token itself.
| Session | JWT | |
|---|---|---|
| Where state lives | Server (database/cache) | Encoded in the token itself |
| Per-request lookup needed? | Yes | No (signature check only) |
| Instant revocation | Trivial — delete the server-side session | Hard — token remains valid until it expires |
| Scales across multiple servers | Needs shared session storage | Naturally stateless, no shared storage needed |
| Payload readable by the client | No (just an opaque ID) | Yes (Base64-encoded, not encrypted) |
The revocation problem is JWT's real tradeoff
This is the crux of the comparison: a session can be invalidated instantly — delete the row, and the next request with that session ID fails immediately. A JWT, once issued, remains cryptographically valid until its exp claim says otherwise — there's no server-side record to delete, because that's the entire point of statelessness. Logging a user out, or responding to a compromised token, means either waiting out the expiration or maintaining some form of server-side revocation list — which reintroduces the server-side lookup that JWTs were meant to avoid, undermining much of the original appeal.
Why JWTs still get chosen anyway
Stateless verification genuinely simplifies horizontal scaling — any server instance can verify a JWT's signature independently, with no shared session store to keep in sync across a fleet of servers. This matters most for architectures with many stateless services (microservices verifying the same token independently) rather than a single monolithic server, where session storage is a much smaller operational concern.
Common mistakes
- Choosing JWTs by default "because they're modern." Sessions remain a perfectly reasonable, often simpler choice, especially for a single-server or small-scale application where instant revocation matters more than statelessness.
- Using long-lived JWTs with no refresh mechanism. A leaked long-lived token stays valid for its entire lifetime with no way to revoke it early — short-lived access tokens plus a separate, revocable refresh token mitigate this.
- Storing sensitive data in a JWT payload "since it's a token." It's readable by the client and anyone who intercepts it — see the Complete Guide to JWT Tokens.
FAQ
Can a JWT be revoked before it expires?
Not natively — the whole design trades away server-side state, so revoking early requires an additional mechanism like a blocklist, which reintroduces a server-side lookup.
Which scales better across multiple servers, sessions or JWTs?
JWTs, natively — any server can verify a token's signature independently; sessions need a shared store (like Redis) accessible to every server instance handling requests.
Is JWT always more secure than sessions?
No — security depends on implementation details in both approaches; JWTs trade instant revocability for statelessness, which is a tradeoff, not an automatic security improvement.
Decode and inspect JWTs directly in your browser with the JWT Decoder — nothing is transmitted anywhere.