Security
JWT & Sessions
Two ways to answer "who's calling" on every request, and why the one that avoids a database lookup can't be undone before it expires.
SecurityIntermediate6 min readPrerequisites: Authentication vs Authorization
After this lesson, you should be able to:
- Compare a session's server-side lookup with a JWT's self-contained, locally-verified signature, and what each costs per request.
- Explain why a JWT's biggest strength, no lookup needed, is also why it can't be revoked before it expires without adding that lookup back.
- Diagnose why deleting a client's local token doesn't revoke a JWT other servers still consider valid.
- Identify when a genuine requirement, like instant forced logout, rules out a pure stateless JWT design.
At a Glance
- Every request after login needs to answer "who is this" without asking the user to log in again, and there are two fundamentally different ways to answer it: look it up, or verify it.
- A session ID is a reference: the server looks it up against a store on every request, and revoking it is just deleting that row.
- A JWT is self-contained: the server verifies a signature and trusts what's inside, no lookup needed, which is exactly what makes it impossible to revoke before it expires without adding back the lookup it was meant to avoid.
01 The Problem
A service scales to multiple instances behind a load balancer. Sessions stored in memory on one instance mean a user's next request, landing on a different instance, doesn't recognize them, logging them out at random depending on which instance answers. The fix is a shared session store every instance checks, which works, but now every single request pays for a network round trip to that store just to answer "who is this," before the request does anything else.
A JWT removes that round trip. Instead of storing session data server-side, the user's identity and claims go inside a token, signed by the server, and the client holds onto it, sending it with every request. Any instance can verify the signature locally, no lookup, no round trip, no shared store required. Scaling problem solved.
Six weeks later, a user's account gets compromised, or an admin needs to force a logout, or a password change should kill every other session immediately. With sessions, that's a one-line delete from the store, and the very next request fails. With a JWT, there's no store to delete from. The token stays valid, cryptographically, until its own expiration, whether or not the server would still say yes if asked directly, because nobody's asking it anymore. That was the whole point.
02 What It Is
A session-based approach issues a random session ID at login and stores the actual session data, user ID, roles, whatever matters, server-side: in memory, in Redis, in a database. Every request, the server takes that ID, looks up the corresponding session, and confirms it's valid before reading whatever's attached to it. The session store is the source of truth. The ID alone means nothing.
A JWT is a signed, self-contained token. The payload, the user's identity and claims, is embedded directly in the token, and a signature computed by the server at issuance proves it hasn't been tampered with. Verifying a JWT means checking the signature and reading the payload, no lookup against any store, because the token carries everything needed to trust it. That's the entire appeal: any server holding the verification key can check a JWT with zero shared state and zero network round trip.
Storing less state was the whole design goal of a JWT, and it's also the whole problem with revocation. Revoking a session is deleting a row the server was going to check anyway. Revoking a JWT means telling every server that would otherwise trust this token's signature to stop trusting it, specifically, before its natural expiration, and the ways to actually do that, a blocklist of revoked token IDs, a short expiration with forced refresh, all reintroduce a server-side lookup on some or every request, exactly what a JWT was chosen to avoid in the first place.
In Plain Terms
A coat check with a numbered ticket only works because the attendant looks up that number against her own rack every time you come back. Take that ticket away from her, and you've taken your coat back, permanently, with no way for her to intervene. A driver's license works differently: it's proof in your own hand, checkable by anyone who can read it, and revoking one means the state has to tell every officer to stop trusting a card that, by itself, still looks completely valid.
03 How It Works
Rendering diagram…
Rendering diagram…
04 Trade-offs
| Session (server-side lookup) | JWT (self-contained, verified) | |
|---|---|---|
| Per-request cost | A lookup against a shared store on every request | Local signature verification, no lookup, no network round trip |
| Revocation | Instant: delete the session, the very next request fails | Not possible before expiration, without adding back a lookup |
| Horizontal scaling | Needs a shared store every instance can reach | Any instance with the verification key checks a token independently |
| What happens if compromised | Delete the session, done, immediately | Valid until expiration regardless, unless a revocation mechanism was built in that defeats the original point |
Watch out
It's tempting to pick JWTs because "stateless is better" as a blanket rule, faster, more scalable, no shared store to manage. Stateless is exactly why you can't take it back. A short expiration limits the blast radius of a compromised or outdated token, it doesn't eliminate it, and a long expiration for convenience means a stolen token stays valid for exactly as long as you decided it should, no matter what happens after you issued it. If a system needs to force logout, revoke access, or kill a token before it naturally expires, it needs a lookup somewhere, and a JWT with a lookup bolted on for revocation isn't stateless anymore, it's a session with extra steps.
05 Where You'll Actually See This
Neither case study implements authentication, so neither shows a session or a JWT in practice. Worth naming: the moment either needed to identify a caller across multiple requests instead of trusting a single request's payload, this is the exact decision that would come first, before idempotency keys or rate limiting even apply to a specific, identified client.
Authentication vs authorization covers the check this article is actually the mechanism for. That article's "is this a valid session" gate, checked once per request before any object-level authorization starts, is exactly a session lookup or a JWT verification, whichever a given system chose. This article is what's actually happening inside that first gate.
OAuth and OpenID Connect is where the JWT half of this shows up concretely: an OIDC ID token, the thing that actually proves who a user is after a third-party login, is typically a JWT. Everything on this page about verification without a lookup, and the same inability to revoke before expiration, applies to that ID token exactly as written here.
Everything on this page also assumes the token was never readable to anyone but its intended recipient. Encryption and TLS is what actually guarantees that in transit. A session ID or a JWT sent over plain HTTP is just handed to anyone on the network path, and no revocation strategy this article covers helps if the token was stolen before anyone even knew it needed revoking.
How to reason about it
When asked to justify sessions or JWTs, don't just say "JWTs are stateless and scale better." Say what's given up to get that: the ability to revoke access before a token expires without reintroducing the lookup that was removed. A system that needs instant logout, forced revocation on a password change, or the ability to kill a compromised token immediately has a real requirement pulling against statelessness, not an edge case to handle later.
06 Quick Check
A team using JWTs with a 24-hour expiration wants to support 'log out on all devices' as an instant action. They implement it by deleting the user's session data client-side on every device the user is currently logged into. Does this actually revoke the JWTs those devices are still holding?