patterntypescriptMajor
Token Blacklisting for Immediate JWT Revocation
Viewed 0 times
redis v4
token blacklistjwt revocationjtiredislogoutinvalidate tokenimmediate revocation
Problem
JWTs cannot be revoked before they expire. A user who logs out, changes their password, or is banned still has a valid token until expiry.
Solution
Maintain a blacklist of revoked token JTI (JWT ID) claims in Redis with TTL set to the token's remaining lifetime. On every authenticated request, check the blacklist after signature verification. Add to the blacklist on logout or security events.
Why
Checking a Redis key is O(1) and adds minimal latency. Using the JTI (a unique identifier per token) rather than the full token keeps the blacklist compact. TTL auto-cleans expired entries.
Gotchas
- Every JWT must include a unique jti claim for this to work — add it at issuance
- If Redis is unavailable, decide whether to fail open (allow requests) or fail closed (deny all) based on your security requirements
- This approach adds a network round-trip per request — batch validation or local caching can mitigate latency
Code Snippets
Blacklist check in auth middleware and logout handler
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
// During auth middleware, after verifying signature:
async function isTokenBlacklisted(jti: string): Promise<boolean> {
return (await redis.exists(`blacklist:${jti}`)) === 1;
}
// During logout or security event:
async function revokeToken(jti: string, ttlSeconds: number) {
await redis.set(`blacklist:${jti}`, '1', { EX: ttlSeconds });
}
// Example logout route
app.post('/logout', requireAuth, async (req, res) => {
const { jti, exp } = (req as any).user;
const ttl = exp - Math.floor(Date.now() / 1000);
if (ttl > 0) await revokeToken(jti, ttl);
res.json({ success: true });
});Revisions (0)
No revisions yet.