139

I am using jwt plugin and strategy in hapijs.

I am able to create jwt token while login user and authenticate other API using the same token through 'jwt' strategy.

I am setting the token in request.state.USER_SESSION as a cookie where USER_SESSION is a token name. Also, I am not saving these token in the database.

But how can I destroy jwt token at the time of logout?

Please suggest a way.

remix23
  • 2,632
  • 2
  • 11
  • 21
Garima
  • 1,566
  • 2
  • 11
  • 14
  • i see that simply you need to store that token somewhere for example in database and that way you have a unique token and on log out for example you can delete it, And i recommend Redis for this – mod7ex Nov 23 '21 at 09:45

8 Answers8

150

The JWT is stored on browser, so remove the token deleting the cookie at client side

If you need also to invalidate the token from server side before its expiration time, for example account deleted/blocked/suspended, password changed, permissions changed, user logged out by admin, take a look at Invalidating JSON Web Tokens for some commons techniques like creating a blacklist or rotating tokens

Community
  • 1
  • 1
pedrofb
  • 37,271
  • 5
  • 94
  • 142
  • 1
    I am agree with your answer. I am setting the jwt token in cookie by 'reply.state('USER_SESSION', { jwtToken});' where USER_SESSION is cookie name. So can you suggest me what command should I run to clear the cookie from state in hapijs? – Garima Jun 22 '16 at 09:55
  • 2
    You are in server side and you can not force browsers to delete a cookie. But you can set the value to empty and include `expires` field to invalidate the cookie value. See http://stackoverflow.com/questions/5285940/correct-way-to-delete-cookies-server-side. You can also invalidate cookie in client side with javascript `var delete_cookie = function(name) { document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'; };` – pedrofb Jun 22 '16 at 10:06
  • What if the token is stored on the browser as an HTTP-only cookie? Then I can't remove it on the client, but I don't want to invalidate it on the server side either – Chen Ni Aug 11 '21 at 03:37
  • 2
    @ChenNi, removing HTTP-Only cookies can be done on the server side ( the server sends a set-cookie header with a new value). The token will not be invalidated but will be inaccessible – pedrofb Aug 11 '21 at 17:50
  • @pedrofb Yeah that makes sense! Thank you :) – Chen Ni Aug 12 '21 at 02:27
  • How is this a correct answer? You cannot remove http-only cookies on client side programatically. It can be only done on the server side due to security reasons. – Nickon Aug 02 '22 at 11:41
87

You cannot manually expire a token after it has been created. Thus, you cannot log out with JWT on the server-side as you do with sessions.

JWT is stateless, meaning that you should store everything you need in the payload and skip performing a DB query on every request. But if you plan to have a strict log out functionality, that cannot wait for the token auto-expiration, even though you have cleaned the token from the client-side, then you might need to neglect the stateless logic and do some queries. so what's a solution?

  • Set a reasonable expiration time on tokens

  • Delete the stored token from client-side upon log out

  • Query provided token against The Blacklist on every authorized request

Blacklist

“Blacklist” of all the tokens that are valid no more and have not expired yet. You can use a DB that has a TTL option on documents which would be set to the amount of time left until the token is expired.

Redis

Redis is a good option for blacklist, which will allow fast in-memory access to the list. Then, in the middleware of some kind that runs on every authorized request, you should check if the provided token is in The Blacklist. If it is you should throw an unauthorized error. And if it is not, let it go and the JWT verification will handle it and identify if it is expired or still active.

For more information, see How to log out when using JWT. by Arpy Vanyan(credit and reference)

Jamil Noyda
  • 3,321
  • 2
  • 21
  • 26
27

On Logout from the Client Side, the easiest way is to remove the token from the storage of browser.

But, What if you want to destroy the token on the Node server -

The problem with JWT package is that it doesn't provide any method or way to destroy the token.

So in order to destroy the token on the serverside you may use jwt-redis package instead of JWT

This library (jwt-redis) completely repeats the entire functionality of the library jsonwebtoken, with one important addition. Jwt-redis allows you to store the token label in redis to verify validity. The absence of a token label in redis makes the token not valid. To destroy the token in jwt-redis, there is a destroy method

it works in this way :

1) Install jwt-redis from npm

2) To Create -

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) To verify -

jwtr.verify(token, secret);

4) To Destroy -

jwtr.destroy(token)

Note : you can provide expiresIn during signin of token in the same as it is provided in JWT.

Aman Kumar Gupta
  • 2,640
  • 20
  • 18
  • jwt.destroy is not a function – abbasalim Mar 27 '20 at 12:09
  • 9
    jwt does not provide any method to destroy, I have explained this above in detail, therefore i have used jwt-redis package.so, if you look at the code, I have written jwtr.destroy() – Aman Kumar Gupta Mar 29 '20 at 16:35
  • Why not just create a separate place in the default database to store blacklisted tokens? Why bring in Redis? – danefondo Aug 20 '20 at 01:48
  • 1
    If you store all your backlisted token in database then how will you able to destroy the token or verify it's validity without reaching the database. what you have to do then on every incoming request with token you reach the database first with that token if its exists or not and then send error accordingly, but my question is why to access database unnecessarily or store the token which is not required at all, So here comes the jwt-redis that stores the token identifier in redis and then destroy it from there. After destroying it won't be able to verify it. – Aman Kumar Gupta Aug 21 '20 at 16:56
  • For #4 to destroy the proper use is: `jwtr.destroy(token.jti)` – arturasmckwcz Jun 03 '21 at 11:00
  • what if we are using python and not nodejs? like fastapi? – uberrebu Aug 31 '21 at 06:13
6

If you just want to remove the token, it will be simple as removing it from the front end application, In you case clear the cookies that stores the token

On the other hand if you mean to invalidate the token, there is couple of ways to do it, below are some ways

(1) If all the token ever generated is stored in backend, It will be just simple as clearing that storage, if tokens have been mapped to users you can just clear tokens for a particular user.

(2) You can add a date field like "invalidate_before" along with user which should be updated at a event of changing password, logout from all devices etc. Simply update the invalidate_before to currentTime() on such events. Every time a new token is created, add the created time in token payload, to validate the token on incoming request just check if the created time in payload is greater than invalidate_before time for that user in db

(3) When you create a new user, create a secret for just that user, then you can sign every user token with that specific secret, and just like in (2) events like changing password, logout from all devices etc, Should create a new secret. This way also you can invalidate by checking the token signature.

overhead with (2) and (3) is that, validation will be a 2 step process and it involves db reading

EDIT: For (3) you may use a salt instead (final secret will be common secret + salt for particular user), So that you hava a way to invalidate either a single user's token by changing salt or the all user's token by changing common secret

Akshay Som
  • 121
  • 1
  • 5
4

You can add "issue time" to token and maintain "last logout time" for each user on the server. When you check token validity, also check "issue time" be after "last logout time".

rabolfazl
  • 435
  • 1
  • 8
  • 24
0

While other answers provide detailed solutions for various setups, this might help someone who is just looking for a general answer.

There are three general options, pick one or more:

  1. On the client side, delete the cookie from the browser using javascript.

  2. On the server side, set the cookie value to an empty string or something useless (for example "deleted"), and set the cookie expiration time to a time in the past.

  3. On the server side, update the refreshtoken stored in your database. Use this option to log out the user from all devices where they are logged in (their refreshtokens will become invalid and they have to log in again).

Magnus
  • 17,157
  • 19
  • 104
  • 189
0

If you are using passportJs jwt strategy, you can invalidate tokes by changing the secretOrKey. It will invalidate tokens of all users.

Pretty sure the auth library you are using has some similar property.

Anton Mi
  • 1
  • 2
-2

OK so I tried something that I wanna share I think it's a really easy and effective method so basically instead of destroying your token or blacklist it we can simply append a random value to it in the middle in a random index or even in the end of it like a random number (or a random hashed number) to make it harder for anyone to reverse it and obtain the previously valid token, Doing so makes this token invalid so the user won't go anywhere and from the front-end you can redirect the user to login again (or even from the back-end however I prefer if the front-end did it) so the user logs out they get redirected to the login page and it's all good, Here's my code. first of all I have an auth middleware that if the token(password & username) is OK it appends the token to req.token so whenever I call this middleware the user's token will be save to req.token

router.post('/logout', auth, async(req, res) => {
    try{
        let randomNumberToAppend = toString(Math.floor((Math.random() * 1000) + 1));
        let randomIndex = Math.floor((Math.random() * 10) + 1);
        let hashedRandomNumberToAppend = await bcrypt.hash(randomNumberToAppend, 10);
    
        // now just concat the hashed random number to the end of the token
        req.token = req.token + hashedRandomNumberToAppend;
        return res.status(200).json('logout');
    }catch(err){
        return res.status(500).json(err.message);
    }
});

right now it will concat the the hashed random number to the end of the token which means it's no longer valid so the user will have to login again as they will be redirected to the login page

  • How does token verifying middleware look like? – arturasmckwcz Jun 02 '21 at 09:33
  • 1
    const jwt=require("jsonwebtoken"); const User=require("../models/usersModel"); const auth=async(req,res,next)=>{ try{ const token = req.headers.authorization.replace("Bearer ",""); const decode = jwt.verify(token,"secret"); const user=await User.findOne({ _id:decode._id }); if(!user){ throw new Error() } req.token=token; req.user=user; next() }catch(error){ return res.status(401).json('Unauthorized access'); } } module.exports=auth – Abdelrhmanshokr Jun 07 '21 at 11:03
  • 3
    Looks like token from headers is placed into request object. But request object lives only while the particular request is processed. What will hapen if the next HTTP request comes with the same token? – arturasmckwcz Jun 07 '21 at 11:32