Working with Token Revocation
1. Understanding Token Revocation Need
| Trigger | Required Action |
| Logout | Invalidate current token(s) |
| Password change | All tokens for user |
| Account compromise | All tokens + force re-auth |
| Permission downgrade | All tokens (claims stale) |
| Client app removal | All tokens for that client |
2. Implementing Token Blacklist
Example: JWT jti blacklist
// On revoke
async function revoke(jwt) {
const { jti, exp } = decode(jwt);
const ttl = exp - Math.floor(Date.now()/1000);
if (ttl > 0) await redis.set(`bl:${jti}`, "1", "EX", ttl);
}
// On every request
async function isRevoked(jti) {
return Boolean(await redis.exists(`bl:${jti}`));
}
3. Using Redis for Revocation Storage
| Pattern | Key | Value / TTL |
| Single token | bl:<jti> | 1 / token exp |
| All user tokens | user:<id>:logout_after | timestamp / max token TTL |
| Family rotation | fam:<id> | revoked flag / 30d |
Note: The "logout after" pattern checks iat < logout_after — avoids storing every jti.
4. Implementing Token Introspection Endpoint
Example: RFC 7662 introspection response
POST /oauth/introspect
token=eyJhbGc...&token_type_hint=access_token
{
"active": true,
"scope": "read write",
"client_id": "app_123",
"username": "alice",
"exp": 1715000000,
"iat": 1714999100,
"sub": "user_42",
"aud": "api://orders",
"iss": "https://auth.example.com",
"jti": "abc-123"
}
| Field | Meaning |
| active | Only required field — false if revoked/expired |
| Auth | Endpoint requires resource server credentials |
| Caching | Cache active=true for short window (e.g., 30 sec) |
5. Revoking All User Tokens
Example: User-level revocation timestamp
// Bump user's revocation timestamp
user.setTokenRevokedAfter(Instant.now());
userRepo.save(user);
// Validator
if (jwt.getIssuedAt().isBefore(user.getTokenRevokedAfter()))
throw new InvalidTokenException();
6. Implementing OAuth Token Revocation
| Aspect | RFC 7009 |
| Endpoint | POST /oauth/revoke |
| Body | token=...&token_type_hint=refresh_token |
| Auth | Client credentials |
| Response | 200 OK (even for unknown tokens — prevents enumeration) |
| Refresh revoke | SHOULD also invalidate associated access tokens |
7. Handling Revoked Token Requests
| Response | Detail |
| Status | 401 Unauthorized |
| Header | WWW-Authenticate: Bearer error="invalid_token", error_description="Token revoked" |
| Body | Minimal — no leakage of why |
| Client | Trigger refresh; on refresh failure → re-auth |
8. Setting Token Expiration vs Revocation
| Strategy | Pros | Cons |
| Short exp only | Stateless, simple | Window of validity post-revoke |
| Long exp + blacklist | Instant revoke | Stateful, extra lookup |
| Hybrid (recommended) | Short access + revocable refresh | Best of both |
9. Implementing Token Status Checking
| Method | Latency | Freshness |
| Local JWT verify | μs | Stale (until exp) |
| Introspection | ms | Real-time |
| Introspection + cache | μs–ms | Configurable window |
| JWT + blacklist check | μs (Redis) | Real-time |
| Technique | Benefit |
| Bloom filter | Memory-efficient probabilistic check |
| Per-user timestamp | O(1) check vs scanning jti list |
| Redis TTL | Auto-expiry of revocations |
| Sharding | Partition by user/token hash |
| Edge caching | Push revocation list to CDN/edge |