Token Blacklisting in Node.js


Table of Contents

  1. Introduction to Token Blacklisting
  2. Why Token Blacklisting is Important
  3. Common Use Cases for Token Blacklisting
  4. How Token Blacklisting Works
  5. Implementing Token Blacklisting in Node.js
    • Using a Database
    • Using Redis for Blacklist Storage
  6. Best Practices in Token Blacklisting
  7. Conclusion

1. Introduction to Token Blacklisting

Token blacklisting refers to the process of invalidating specific authentication tokens, such as JWTs (JSON Web Tokens), before their expiry time. It ensures that tokens that have been compromised, revoked, or are otherwise no longer valid can be immediately rejected by the system.

In many applications, tokens are used for user authentication and authorization, particularly with stateless systems like REST APIs. However, there are scenarios where you might need to revoke a user’s access immediately, for example, when they log out, change their password, or their account is compromised.

While JWTs are designed to be stateless (without server-side session storage), token blacklisting introduces server-side tracking of tokens that should no longer be valid, adding a layer of security for critical use cases.


2. Why Token Blacklisting is Important

Blacklisting tokens is important because:

  • Revoking Access: If a user logs out, their token should no longer be valid.
  • Compromised Accounts: If a user’s account is compromised, you need to invalidate all active tokens associated with that user.
  • Sensitive Actions: After sensitive actions (like changing the password or email address), you may want to invalidate all tokens for security purposes.

Without token blacklisting, once a token is issued, it is valid until it expires, leaving a window of vulnerability for malicious actors to misuse the token.


3. Common Use Cases for Token Blacklisting

Some common use cases for token blacklisting include:

  • User Logout: When a user logs out, invalidate their current token so it can’t be reused.
  • Password Change: After a password change, all tokens should be invalidated, as the previous tokens were generated under the old password.
  • Account Compromise: If an account is compromised, all associated tokens need to be revoked immediately.
  • Administrative Action: When an admin performs certain actions like disabling an account, associated tokens should be blacklisted.

4. How Token Blacklisting Works

Token blacklisting typically works by storing the token’s unique identifier (often the token’s ID or the entire token itself) in a centralized store, such as a database or Redis. When a request is made to a protected route, the token is checked against the blacklist. If the token exists in the blacklist, it is rejected, and the user is denied access.

1. JWT Token Blacklisting Strategy

Since JWTs are stateless, they don’t require a central server-side session, which makes blacklisting tricky. The common approach is:

  • Generate a unique token identifier when the JWT is created.
  • Store this identifier in a blacklist whenever the token needs to be revoked (e.g., on logout).
  • When a request is made with a token, check the token’s identifier against the blacklist before allowing access.

5. Implementing Token Blacklisting in Node.js

Let’s explore how to implement token blacklisting in a Node.js application using a Database and Redis for fast lookups.

Using a Database (MongoDB Example)

  1. Install Required Packages
npm install express jsonwebtoken mongoose
  1. Create a Model for Blacklisted Tokens

First, create a Mongoose model to store the blacklisted tokens.

const mongoose = require('mongoose');

const BlacklistedTokenSchema = new mongoose.Schema({
tokenId: String, // The unique identifier for the JWT token
createdAt: { type: Date, default: Date.now },
});

const BlacklistedToken = mongoose.model('BlacklistedToken', BlacklistedTokenSchema);
module.exports = BlacklistedToken;
  1. Blacklist Token After Logout

When the user logs out, add the token ID to the blacklist.

const jwt = require('jsonwebtoken');
const BlacklistedToken = require('./models/BlacklistedToken');

function blacklistToken(token) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti; // Using the "jti" claim as a unique identifier

const blacklistedToken = new BlacklistedToken({ tokenId });
blacklistedToken.save();
}

// Example of logging out and blacklisting the token
app.post('/logout', (req, res) => {
const token = req.headers['authorization'].split(' ')[1];
blacklistToken(token);
res.send('Logged out successfully');
});
  1. Check for Blacklisted Tokens

When processing requests, check if the token is blacklisted.

async function isTokenBlacklisted(token) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti;

const blacklistedToken = await BlacklistedToken.findOne({ tokenId });

return !!blacklistedToken;
}

app.get('/protected', async (req, res) => {
const token = req.headers['authorization'].split(' ')[1];

// Check if the token is blacklisted
if (await isTokenBlacklisted(token)) {
return res.status(401).send('Token is blacklisted');
}

// Process the request if the token is not blacklisted
res.send('Protected data');
});

Using Redis for Token Blacklisting

Redis is an ideal solution for managing blacklisted tokens because of its fast in-memory data store capabilities.

  1. Install Redis and Connect it to Node.js
npm install redis express jsonwebtoken
  1. Set Up Redis Client
const redis = require('redis');
const client = redis.createClient();
client.on('connect', () => console.log('Connected to Redis'));
  1. Blacklist a Token in Redis

When a user logs out or when their token needs to be invalidated, you can store the token in Redis with a time-to-live (TTL) value to ensure that tokens are not blacklisted forever.

function blacklistTokenInRedis(token) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti; // Using the "jti" claim as a unique identifier

client.setex(tokenId, 3600, 'blacklisted'); // Set token ID with a TTL of 1 hour
}

// Example of logging out and blacklisting the token
app.post('/logout', (req, res) => {
const token = req.headers['authorization'].split(' ')[1];
blacklistTokenInRedis(token);
res.send('Logged out successfully');
});
  1. Check if Token is Blacklisted in Redis

When processing a request, check if the token ID is in the blacklist.

function isTokenBlacklistedInRedis(token, callback) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti;

client.get(tokenId, (err, reply) => {
if (err) throw err;
callback(reply !== null);
});
}

app.get('/protected', (req, res) => {
const token = req.headers['authorization'].split(' ')[1];

// Check if the token is blacklisted in Redis
isTokenBlacklistedInRedis(token, (isBlacklisted) => {
if (isBlacklisted) {
return res.status(401).send('Token is blacklisted');
}

// Process the request if the token is not blacklisted
res.send('Protected data');
});
});

6. Best Practices in Token Blacklisting

  • Use Expiry Time for Blacklisted Tokens: Set a time-to-live (TTL) for blacklisted tokens to avoid the blacklist growing indefinitely. For example, a token that was logged out could remain blacklisted for an hour before being automatically removed.
  • Efficient Lookups: Use fast in-memory data stores like Redis for efficient lookups. Avoid storing blacklisted tokens in a traditional relational database if high scalability is needed.
  • Secure Token Storage: Always use secure HTTPS connections to protect tokens during transit. Also, store tokens securely on the client-side (in HttpOnly cookies or localStorage) to prevent unauthorized access.

7. Conclusion

Token blacklisting provides an essential security feature by allowing you to revoke or invalidate tokens before their expiration time. Whether using a database or Redis, blacklisting ensures that compromised or unwanted tokens can be efficiently rejected during requests. By implementing token blacklisting correctly, you can improve your application’s security and protect sensitive data.