Why You CANNOT Truly Invalidate a JWT
π― The Questionβ
"A user clicks 'Logout', or an administrator bans a malicious user account. If your backend uses pure stateless JSON Web Tokens (JWTs), why can the attacker continue using that JWT to access protected API endpoints? Why is true token invalidation impossible without reintroducing state?"
β‘ 30-Second Elevator Pitchβ
The core selling point of a JSON Web Token (JWT) is that it is Stateless and Self-Contained:
- The token contains its own payload (
sub,role,expexpiration timestamp). - Any microservice can verify authenticity simply by mathematically validating the cryptographic signature using a public key or secretβwith zero database queries.
The Fundamental Flaw:
Because the server does not store active sessions in a database, it has no record to delete when a user logs out.
As long as the token signature is valid and current time is less than exp, every API server in your infrastructure will accept it as authorized.
π§ Under-the-Hood: The Stateless Verification Trapβ
π¬ How Real-World Systems Invalidate JWTsβ
To solve this security problem in production, architectures combine stateless performance with minimal state tracking:
1. Short-Lived Access Tokens + Refresh Tokens (Industry Standard)β
- Access Token: Valid for only 5 to 15 minutes. Even if stolen, the vulnerability window is tiny.
- Refresh Token: Stored in a secure
HttpOnlycookie and recorded in a database. - To revoke access, delete the Refresh Token from the database. When the access token expires 10 minutes later, the client cannot refresh.
2. Redis Token Blacklisting (Deny-List)β
When a user logs out, push the token's jti (JWT ID) into Redis with a TTL equal to the token's remaining lifespan. Services check Redis on sensitive operations.
3. User Token Versioning (token_version)β
Store an integer token_version on the user's database record and embed it inside the JWT payload.
- When a user changes passwords or logs out, increment
user.token_version = token_version + 1. - During auth checks, if
jwt.token_version != user.token_version, reject the token.
π Comparison Matrix: Stateless JWT vs. Stateful Sessionsβ
| Dimension | Pure Stateless JWT | Stateful Session (Redis / DB) | Hybrid (Short JWT + Refresh Token) |
|---|---|---|---|
| Instant Revocation | β Impossible | β Instant (Delete session key) | β Revocation within 5β15 mins |
| Database Load | β‘ Zero DB lookups | Requires lookup per request | Lookup only on token refresh |
| Payload Size | Large (~500β1000 bytes in headers) | Tiny (~32-byte session ID cookie) | Small access token |
| Stolen Token Risk | Valid until exp timestamp | Revocable immediately | Bounded to short lifespan |
π‘ What Interviewers Ask Next (Follow-Up Traps)β
-
"If we use a Redis Blacklist for JWTs, haven't we just reinvented sessions?"
- Answer: Yes, partially. You sacrifice pure statelessness. However, a blacklist is much smaller than a full session store: it only stores tokens for logged-out or banned users (a small fraction of traffic), rather than storing a session for every single active user in the system.
-
"Why shouldn't you store JWTs in browser
localStorage?"- Answer: Any JavaScript running on the page can access
localStorage. If your app has a Cross-Site Scripting (XSS) vulnerability, an injected script can steal the JWT. Store authentication tokens inHttpOnly,Secure,SameSite=Strictcookies so browser JavaScript cannot read them.
- Answer: Any JavaScript running on the page can access
Interview Answer: Pure JWTs cannot be invalidated on demand because they are stateless. Resource servers verify tokens purely by checking the cryptographic signature without querying a central session database. Real-world systems solve this by pairing short-lived access tokens (10 mins) with revocable refresh tokens, token versioning, or Redis blacklists.