In this article, I will be sharing briefly how to invalidate JWT tokens on user logout (backend implementation) using Redis.
Redis is an in-memory data structure store that can be used as a database, message-broker, or cache. I will be using the sorted set data structure of Redis.
Sorted sets in Redis allows you to save a member and its score in the Redis sorted set.
Think of the sorted set as an object in javascript or dictionary in python, where the key is the score and the value is the member.
For the sake of our goal, I will be assuming the score of each member (in this case the JWT token) of the sorted set as the expiry date or time of such JWT token.
NB: This is not a full code implementation.
- Issue Token When the token is issued to the user on login/signup depending on your implementation. Save the token on Redis.
i. Create a token that expires within 24hrs:
Something that looks like this:
//a. Ensure the user exist and the password is correct
//b. Issue the token if (a) is okay
const token = ({userId, email}) => {
return jwt.sign({
data: {id, email}
}, `${process.env.SECRET}` , { expiresIn: '24h' });
}
//c. Add the token to redis using the userId as the token key
const tokenKey = new Date(Date.now() + (1000*60*60*24));
await redis.zadd(`${userId}-tokens`, `${tokenKey}`, token);
//d. Send the token to the user
ii. Check and remove invalid tokens on user login (you could use a cron
job for this instead):
Get expired tokens for this user on Redis and remove them
const expiredTokens = await redis.zrangebyscore(`${user.id}-tokens`,
`-inf`, Date.now());
// Remove expired tokens on user login/use a cronjob
expiredTokens.length >= 1 && expiredTokens.forEach(async token => {
await redis.zrem(`${user.id}-tokens`, token)
});
- Remove specific token on user logout: So what I want is to not log out all devices the user might be connected with (you might do this if you have a feature to logout all devices feature), we want to logout the specific device that has this token, and ensures the token can never be used again even if it has not expired yet.
// Get the token sent from the frontend
const {token} = args;
const isTokenValid = await verifyToken(token)
if(isTokenValid){
const {userId} = isTokenValid.data
const allTokens = await
redis.zrangebyscore(`${userId}-tokens`, `-inf`,
`+inf`);
if (allTokens.includes(token)){
await redis.zrem(`${validateToken.data.id}-tokens`, token);
return Response({status: 'Success'})
}
}
return Response({status: 'Failure'})
What this means: On every user request for content that might need authentication, you should ensure that such token is in the user's Redis store i.e ${userId}-tokens
. If the token is not in the Redis store, then it is not a valid token anymore.
I do understand that this might not be a perfect implementation, I am therefore open to any constructive feedback.
Credits:
Top comments (0)