Card 09/ 12
Revoking a JWT Before It Expires
Logging a user out does not stop a stolen JWT, which stays valid until its exp.
A self-contained JWT is accepted by checking its signature and times, with no lookup, so nothing the issuer does afterwards reaches it. Signing a user out, disabling their account or noticing a stolen token leaves the JWT working until its exp. Revoking one early needs a design chosen in advance: short lifetimes, a deny list checked on each request, or asking the issuer.
Picture an employee who left on Friday afternoon. Their account was disabled at 17:00, and on Saturday morning their token, issued with a 24-hour lifetime, still read customer records.
The symptom. Requests from a disabled or signed-out user keep succeeding for hours after access was removed.
| Approach | Delay before it stops | Cost per request | Needs |
|---|---|---|---|
| Short access tokens and a revocable refresh token | Up to one access-token lifetime, such as 5 to 15 minutes | None | Refresh flow; the refresh token is checked at the issuer |
| A deny list of jti values | Immediate | One fast lookup | A shared store every API reads |
| Introspection at the issuer | Immediate | One call to the issuer, or a cached answer | An introspection endpoint, RFC 7662 |
| Rotating the signing key | Immediate for all tokens | None | Accepting that every user is signed out |
Why it happens. The property that makes JWTs fast, verification without a lookup, is the same property that makes them deaf to later changes.
What it costs. A window, as long as the token's lifetime, in which a stolen token or a removed user still has access.
What to do. Keep access tokens short, minutes rather than hours, and pair them with a refresh token the issuer can revoke; most systems need nothing more. Where a minute of exposure is too much, such as administrative actions, check a deny list or introspect. Deleting the token from a database does nothing, because nothing was stored to delete.
← → move through sections and cards