When building secure APIs with JWT (JSON Web Tokens), handling user logout can be tricky. Since JWT is stateless, there’s no out-of-the-box way to invalidate tokens after logout. That’s where blacklists and tools like Redis come into play. If you’re new to these concepts, don’t worry! This guide will explain everything step by step and help you implement a practical solution.
Understanding Statelessness and JWT
Stateless Systems
- Stateless systems don’t store any user session information on the server.
- Each request carries all the necessary data (e.g., a JWT) for the server to process it.
JWT
- JWT contains user data (like
id
androle
) and is signed by the server. - Once issued, the server doesn’t need to store the token or session details.
- Problem: If a user logs out, their JWT remains valid until it expires.
What is a Blacklist?
A blacklist is a list of tokens that have been invalidated. When a user logs out, their token is added to this list. Every time a request is made, the server checks if the token is in the blacklist. If it is, the request is rejected.
Steps to Implement a Blacklist:
- Store invalidated tokens in a data structure (e.g., an array,
Set
, or database). - When processing requests, verify the token is not in the blacklist.
- Add a cleanup mechanism to remove expired tokens from the blacklist.
Why Redis?
Redis is a high-performance, in-memory key-value database. It’s perfect for use cases like blacklisting JWTs because:
- Speed: Redis can handle thousands of read/write operations per second.
- Distributed: Multiple servers can share the same Redis instance for consistent data.
- TTL (Time-to-Live): Redis can automatically remove entries after a specified duration.
How to Start Without Redis
If you’re new to these concepts, start with a simple in-memory solution:
const blacklist = new Set();
// Add token to blacklist
authController.logout = (req, res) => {
const token = req.headers.authorization.split(" ")[1];
blacklist.add(token);
res.status(200).json({ message: "Logged out successfully" });
};
// Middleware to check token validity
middleware.verifyToken = (req, res, next) => {
const token = req.headers.authorization.split(" ")[1];
if (blacklist.has(token)) {
return res.status(401).json({ message: "Invalid token" });
}
next();
};
This approach works for small-scale projects but has limitations. If your app scales, you’ll need a more robust solution like Redis.
Getting Started with Redis
1. Install Redis
- Install Redis locally: Redis Installation Guide
- Alternatively, use a cloud service like AWS Elasticache or Redis Cloud.
2. Integrate Redis in Node.js
Use the ioredis
library to interact with Redis in your Node.js app:
npm install ioredis
const Redis = require("ioredis");
const redis = new Redis();
// Add token to Redis with TTL
const logout = async (req, res) => {
const token = req.headers.authorization.split(" ")[1];
const expiresIn = 3600; // 1 hour
await redis.setex(token, expiresIn, "blacklisted");
res.status(200).json({ message: "Logged out successfully" });
};
// Middleware to check Redis blacklist
const verifyToken = async (req, res, next) => {
const token = req.headers.authorization.split(" ")[1];
const isBlacklisted = await redis.get(token);
if (isBlacklisted) {
return res.status(401).json({ message: "Invalid token" });
}
next();
};
Redis vs In-Memory
Feature | In-Memory (Set ) |
Redis |
---|---|---|
Scalability | Limited to a single server | Distributed across servers |
Speed | Very fast | Equally fast |
Persistence | Lost on server restart | Data persists across restarts |
Cleanup | Manual | Automatic with TTL |
Next Steps
-
Learn Redis Basics: Understand commands like
SETEX
,GET
, andDEL
. - Secure Redis: Use authentication and IP whitelisting.
- Optimize Blacklist: Store only token hashes for added security.
Starting simple with an in-memory solution and gradually transitioning to Redis ensures you don’t get overwhelmed. Happy coding!
Let me know in the comments if you have any questions or need help with Redis setup. 🚀
Top comments (0)