← Blog
May 25, 2026 · 9 min

JWT Revocation Strategies That Work

JWTs are stateless by design, which makes revocation hard. Here are the denylist, short-expiry, and versioning strategies that actually work in production.

The fundamental tension JWTs are designed to be verified without a database lookup — that's the entire point of a self-contained, signed token. Revocation, by definition, requires checking some external state ("has this been invalidated?"), which works against that design goal. There is no free lunch here: any revocation strategy reintroduces some amount of statefulness.

Strategy 1: Short expiration, and just wait it out The simplest strategy is to make access tokens so short-lived (1–15 minutes) that revocation is rarely needed — a compromised or logged-out token simply stops working shortly on its own. This handles the vast majority of real-world cases and pairs naturally with a revocable refresh token for the actual "log out" action.

Strategy 2: Refresh token revocation Since refresh tokens are typically opaque and database-backed (see our refresh token post), revoking a session is as simple as deleting or flagging that record. The access token issued from it will still work until it naturally expires, but no new one can be minted — bounding the damage to one token lifetime.

Strategy 3: A denylist for immediate revocation When you need a token to stop working **immediately** (a detected breach, a stolen device reported), maintain a fast lookup — Redis is the typical choice — of revoked `jti` values or user IDs, checked on every request. This reintroduces a database/cache round trip per request, undermining some of the "stateless" benefit, but it's the only way to get true immediate revocation for an already-issued access token.

  • Store the jti or sub, plus an expiry equal to the token's remaining lifetime, so the denylist entry can be cleaned up automatically.
  • Check the denylist as an additional step after signature verification, not instead of it.

Strategy 4: Token versioning per user Add a `tokenVersion` (or similar) claim to the JWT, and store the current valid version per user in your primary database. To revoke all of a user's tokens at once (e.g., after a password change), increment the stored version; any token presenting an older version fails verification. This requires a lookup per request too, but it's a single indexed read you likely already have cached alongside the user record.

Strategy 5: Global key rotation as a last resort Rotating the signing key immediately invalidates every token signed with the old key. This is a blunt instrument — it logs out every user in your system — and is appropriate for a suspected key compromise, not routine logout.

Choosing a strategy - Routine logout → revoke the refresh token, let the short-lived access token expire naturally. - Password change or account compromise → increment token version and revoke refresh tokens. - Confirmed key/secret leak → rotate signing keys immediately. - Need instant per-token revocation regardless of cause → denylist by `jti`.

Checklist - Never rely on JWT expiration alone as your only revocation mechanism for sensitive actions. - Keep access tokens short enough that "wait it out" is a viable fallback. - Always make refresh tokens revocable — they're your real logout control. - Add a version or `jti` claim if you need faster-than-expiry revocation. - Test your logout flow end to end — a JWT that "still works after logout" for its natural lifetime is expected, not a bug, unless you've implemented one of the above.