🛡️ Is JWT Really Stateless? A Common Misconception and Its Solution
JSON Web Tokens (JWT) are often described as stateless. While that’s technically true in how they’re designed, taking it literally can create serious security challenges.
If JWTs are completely stateless, how do we handle real-world scenarios like Logout, Password Change, or Multi-Device Logins? Let’s dig deeper.
🛂 JWT vs Passport: A Simple Analogy
Think of a passport. If it only had your name and age, but no issuing authority, country, expiry date, or passport number—would it be trustworthy?
📌 Answer: No, because it lacks the foundation of trust.
JWTs work in the same way. Just storing user_id or role in the payload isn’t enough. Without additional claims and context, JWTs can easily become a security risk.
🛑 The Limitations of Stateless JWT
A purely stateless JWT, once issued, remains valid until its expiry (exp). This causes a few serious problems:
- Logout doesn’t work → Even after a user logs out, their token is still valid until it expires.
- Password changes don’t invalidate sessions → Old tokens can still be used, even after resetting a password.
- Uncontrolled multi-device logins → The same account can be logged in across multiple devices without restrictions.
These issues make JWTs insecure if we rely only on their stateless nature.
✅ The Solution: Add State to Stateless
JWTs become truly secure when we combine stateless authentication with a little bit of state. This is usually managed with an in-memory database like Redis.
For example, you can maintain two maps (Redis in production):
blacklistedTokens // revoked tokens
activeUserTokens // user → single active token
1️⃣ Handling Logout
When a user logs out, add the token’s jti (JWT ID) to a blacklist.
Each request checks if the token is blacklisted:
blacklistedTokens[tokenID] = true
if IsTokenBlacklisted(tokenID) {
return 401
}
2️⃣ Preventing Multiple Device Logins
When a user logs in, revoke old tokens and allow only the latest one:
deleteOldUserTokens(userID)
activeUserTokens[userID] = newTokenID
3️⃣ Invalidating Sessions After Password Change
When a password is reset, revoke all active tokens for that user:
DeleteAllUserTokens(userID)
This hybrid approach ensures JWTs remain secure in real-world use cases.
🔐 Essential JWT Claims for Better Security
To make JWTs as reliable as a passport, always include key claims:
- jti (JWT ID): Unique identifier for each token (used for revocation).
- iss (Issuer): The system or authority that issued the token.
- aud (Audience): The intended recipient app/service (e.g., web, mobile).
- exp (Expiration Time): Short token lifetime (e.g., 1–2 hours) to limit damage if stolen.
📣 Final Thoughts
JWTs are not fully stateless in real-world applications.
To ensure security, combine JWTs with stateful mechanisms like blacklisting, session management, and proper claim usage.
👉 How do you handle JWT security in your projects? Share your thoughts in the comments!