TL;DR
Invalidating JWTs on password changes and logout requires a blacklist (or revocation list). Store invalidated tokens (e.g., token IDs) server-side, and check this list during each request before trusting a JWT. This guide shows how to implement it in Node.js.
1. Understanding the Problem
JWTs are stateless. Once issued, they’re valid until their expiry time. Simply changing a password or logging out doesn’t automatically invalidate existing tokens. Without extra measures, users could continue using old tokens even after their credentials have been compromised or their session should be terminated.
2. Implementing a Token Blacklist
We’ll use an in-memory list for simplicity. For production, consider Redis or another persistent store.
Step 1: Store Invalidated Token IDs
When a user changes their password or logs out, add the JWT ID to the blacklist.
const jwtBlacklist = [];
// Example function to invalidate token on logout
function invalidateToken(tokenId) {
jwtBlacklist.push(tokenId);
}
Step 2: Middleware for Token Validation
Create middleware that checks the blacklist before verifying the JWT.
const jwt = require('jsonwebtoken');
function verifyToken(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(401).send('No token provided.');
}
const token = authHeader.split(' ')[1]; // Bearer token format
// Check if the token is in the blacklist
if (jwtBlacklist.includes(token)) {
return res.status(403).send('Token has been invalidated.');
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Attach user data to the request
next();
} catch (err) {
return res.status(401).send('Invalid token.');
}
}
3. Password Change Workflow
- User requests a password change.
- Verify the user’s identity (e.g., current password).
- Update the password in the database.
- Invalidate all tokens associated with that user. This is crucial! You can achieve this by invalidating tokens based on the user ID embedded within them.
Example (assuming your JWT payload includes a ‘userId’ claim):
// Get all tokens for the user and invalidate them.
function invalidateUserTokens(userId) {
// This is a simplified example; in reality, you might query a database
// to find all tokens associated with this userId.
const tokensToInvalidate = []; // Replace with actual token retrieval logic
tokensToInvalidate.forEach(token => {
invalidateToken(token);
});
}
4. Logout Workflow
- User initiates logout (e.g., clicks a ‘Logout’ button).
- Invalidate the current token by adding its ID to the blacklist.
- Optionally, clear any client-side storage of the token (e.g., localStorage, cookies).
Example:
// In your logout route handler:
app.post('/logout', verifyToken, (req, res) => {
const token = req.headers['authorization'].split(' ')[1];
invalidateToken(token);
res.send('Logged out successfully.');
});
5. Important Considerations
- Token ID Extraction: Ensure you can reliably extract a unique token ID from the JWT (e.g., using the `jwt.decode()` function).
- Scalability: For high-traffic applications, an in-memory blacklist won’t scale. Use Redis or another distributed cache.
- Security: Protect your blacklist itself! Unauthorized access could allow attackers to invalidate legitimate tokens.
- Refresh Tokens: Consider using refresh tokens alongside JWTs for a more secure and user-friendly experience. Invalidating the refresh token is also important during logout or password changes.

