JWTs Need No Database Lookup? Revocation, Logout, and Why Only Half True
- Author
- Vishal Maurya
- Published on
- Reading time
- 4 min read
Overview
A common claim is that JWTs let you authenticate requests without touching a database. It is true for verification: the server checks the signature and expiry. But then a user clicks "log out", an admin disables an account, or a token leaks, and you discover that a valid signature says nothing about whether the token should still be accepted.
This post covers the practical ways to regain control, and what each one costs.
1. Why Logout Is Hard With Stateless Tokens
A self-contained JWT is valid until its exp claim passes. "Logout" on the client just deletes the token from storage. Anyone who copied the token can still use it until it expires.
So a purely stateless design cannot support immediate revocation. Any revocation mechanism adds server-side state, and that state needs to be consulted on requests. You are trading the stateless benefit for control, and the question is how much state you need.
2. Option A: Short TTL Plus Refresh Tokens
Make access tokens short-lived, for example a few minutes, and keep revocation on the refresh token, which is tracked in your database. When you revoke a session, the refresh token stops working, and the access token dies on its own shortly after.
This accepts a bounded delay. If a few minutes of continued access after logout or ban is acceptable, this is the simplest approach and keeps most requests free of lookups. If it is not acceptable, combine it with one of the options below.
3. Option B: Denylist by Token ID
Give each token a unique jti claim. On logout, store that ID in a fast store until the token's natural expiry, then check the denylist on each request.
import time
import redis
r = redis.Redis()
def revoke_token(jti: str, exp: int) -> None:
ttl = exp - int(time.time())
if ttl > 0:
r.setex(f"revoked:{jti}", ttl, "1")
def is_revoked(jti: str) -> bool:
return r.exists(f"revoked:{jti}") == 1
The list stays small because entries expire with the token. The cost is a store lookup per request, so you now have a dependency in the hot path. It is a cheap lookup, but the claim "no database" no longer holds.
4. Option C: Token Versioning
Store a token_version (or session_epoch) on the user record and embed it in the token. On each request compare the claim with the current value. To log a user out everywhere, or after a password change or role change, increment the version, which invalidates every outstanding token at once.
This requires reading the user's current version, so it needs a lookup, but that can be cached for a short time. It is well suited to "revoke everything for this user", and less suited to revoking one specific device unless you version per session.
5. Option D: Introspection
With opaque tokens or when many resource servers need a central answer, the resource server can call an introspection endpoint to ask whether a token is active. OAuth 2.0 defines this in RFC 7662. It gives you immediate and accurate revocation at the price of a network call, so consider caching responses briefly.
6. Choosing
- Revocation within minutes is fine: short TTL plus refresh token revocation.
- Per-session logout must be instant: denylist by
jti, or just use server-side sessions. - Revoke all of a user's tokens on password or permission changes: token versioning.
- Many services need an authoritative answer: introspection.
If you find yourself adding a lookup on every request, ask whether server-side sessions would have been simpler. They give you revocation by default.
7. Related Pitfalls
- Role claims baked into a token stay stale until expiry. Keep sensitive authorization decisions server-side or keep TTLs short.
- Always validate
exp, issuer, and audience, and pin the accepted algorithm. - Do not put secrets in the payload. A standard JWT is signed, not encrypted.
- Clock skew can cause spurious failures, so allow a small, explicit leeway.
Conclusion
JWTs avoid a lookup for signature verification, not for deciding whether a token should still be honored. Pick the lightest revocation model that meets your security requirement: short TTLs, a denylist, token versions, or introspection. If you need instant revocation everywhere, sessions may be the better tool.
If you would like help auditing a JWT implementation or designing revocation for your API, I can help. Contact me.