How to Implement JWT Security with Blocklisting in Node.js: A Production-Ready Guide
Implement JWT security with blocklisting in Node.js by maintaining a revocation list of compromised tokens using an external store like Redis or Memcached, then verify each request against this list using middleware such as express-jwt-blacklist integrated with express-jwt.
JSON Web Tokens (JWTs) provide stateless authentication for Node.js applications, but their immutable nature creates a critical security gap when tokens require immediate invalidation. The nodebestpractices repository (goldbergyoni/nodebestpractices) offers a battle-tested pattern for adding revocation capabilities without dismantling the benefits of JWT architecture. According to the guidelines in sections/security/expirejwt.md, implementing a blocklist (also called a revocation list) allows you to invalidate specific tokens on demand while preserving the stateless verification flow for valid tokens.
The Challenge of Stateless Token Revocation
JWTs are inherently stateless, meaning that once a token is issued, its signature remains valid until it expires. If a token is compromised or a user needs to be logged out immediately, the application cannot "revoke" the token without breaking the stateless model. The recommended solution is to add a revocation layer on top of JWT, even if it implies losing its stateless nature for the specific purpose of security enforcement.
Architecture of a JWT Blocklist System
A robust blocklist implementation requires three core components working together:
- External Storage: The default in-memory store is unsuitable for multi-process deployments. Production environments require an external store such as Redis or Memcached.
- Middleware Integration: The blocklist must be consulted on every request through an
isRevokedcallback in your JWT middleware. - Revocation API: An endpoint or function that adds token identifiers to the blocklist when users log out or security incidents occur.
Implementing JWT Blocklisting with express-jwt-blacklist
The nodebestpractices repository demonstrates this implementation using the express-jwt-blacklist module together with express-jwt. This approach treats any token present in the blocklist as invalid while allowing valid tokens to proceed through standard stateless verification.
Configuring the Blocklist Store
Never use the default in-memory store for production deployments. Instead, configure an external storage adapter. The example in sections/security/expirejwt.md demonstrates Memcached configuration, though Redis is equally suitable:
const jwt = require('express-jwt');
const blacklist = require('express-jwt-blacklist');
// Configure the blocklist store (use external storage for production)
// Here Memcached is demonstrated; replace with Redis as needed
blacklist.configure({
tokenId: 'jti', // JWT ID claim used to identify the token uniquely
strict: true, // Enforce strict validation checks
store: {
type: 'memcached',
host: '127.0.0.1',
port: 11211,
keyPrefix: 'mywebapp:',
options: {
timeout: 1000
}
}
});
Integrating Blocklist Verification into Middleware
Supply the isRevoked callback to express-jwt to enable blocklist verification on each request. This allows the middleware to check the external store before processing the token payload:
// Attach JWT middleware with revocation check
app.use(jwt({
secret: 'my-secret', // Replace with your real secret or public key
isRevoked: blacklist.isRevoked // Blocklist verification on each request
}));
Revoking Tokens on Logout
When a user logs out or a security incident requires immediate token invalidation, extract the JWT payload (including the jti claim) and add it to the blocklist:
// Example endpoint to revoke a token (e.g., logout)
app.get('/logout', (req, res) => {
// The JWT payload (including the `jti` claim) is available as req.user
blacklist.revoke(req.user); // Adds the token ID to the blocklist
res.sendStatus(200);
});
Production Considerations
Because the blocklist is consulted on every request, latency to your external store (Redis/Memcached) directly impacts API response times. Monitor connection pooling and timeout settings carefully.
As noted in the nodebestpractices documentation, adding a blocklist "implies losing its stateless nature" for the revocation check. This trade-off is necessary for security-sensitive operations. The README.md file links to the security section providing an overview of the "Support blocklisting JWTs" best practice, emphasizing that the revocation layer sits on top of the standard JWT flow rather than replacing it.
Summary
- JWTs cannot be natively revoked due to their stateless design; a blocklist provides the necessary revocation capability.
- External storage is mandatory for production: use Redis or Memcached instead of in-memory stores to support multi-process deployments.
- Integration requires two steps: configure the blocklist store with a unique token identifier (
jti), then passblacklist.isRevokedto yourexpress-jwtmiddleware. - Revocation is explicit: call
blacklist.revoke(req.user)during logout or security events to invalidate tokens immediately. - Implementation examples are available in
sections/security/expirejwt.mdwithin the goldbergyoni/nodebestpractices repository.
Frequently Asked Questions
What is a JWT blocklist and why do I need one?
A JWT blocklist (or revocation list) is a data store containing identifiers of tokens that should no longer be accepted by your application. You need one because standard JWTs remain valid until their expiration time, meaning compromised tokens cannot be invalidated without a separate tracking mechanism.
Can I use an in-memory store for JWT blocklisting in production?
No. According to the implementation in sections/security/expirejwt.md, the default in-memory store is unsuitable for multi-process deployments where each Node.js instance would maintain a separate, inconsistent blocklist. Always use a shared external store like Redis or Memcached.
How does the jti claim work in token revocation?
The jti (JWT ID) claim provides a unique identifier for each token. When configuring express-jwt-blacklist, setting tokenId: 'jti' tells the middleware which payload field to use as the blocklist key. This allows specific tokens to be revoked individually without affecting other sessions for the same user.
Does using a blocklist make my JWT implementation stateful?
Partially. While valid tokens still follow stateless verification, each request requires a network call to the external blocklist store. As the nodebestpractices documentation states, this adds "a revocation layer on top of JWT, even if it implies losing its stateless nature," but this trade-off is essential for production security requirements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →