How to log out when using JWT

Arpy Vanyan on June 17, 2018

The wonder of JSON Web Tokens JSON Web Tokens (JWT) is a way of statelessly handling user authentication. What does it mean? Well, J... [Read Full]
markdown guide
 

An alternative approach that I've experimented with (so caveats apply) is to keep a 'jwt version number' for each account (in db and/or memory). That is lightweight enough unless you have an enormous amount of users.

Though of course, the first thing to keep in mind is that JWT was simply not designed to work like/with/for this, so any solution we come up with will be a bit of a (conceptual) hack.

 
 
 

A simple "token blacklist" is not enough. It works for logouts, yes, but there are at least two other events in which you want to invalidate jwts:

  • a "log out all devices" event (manual button, password change)
  • a "user no longer valid" event (user deletion)

For those you need to be able to blacklist users and user-date pairs. The user blacklist can be a simple check against the user database to see if the user exists and is active, the user-date can be a check against a date in the user record indicating the date at which Tokens become valid,and if the token's iat field is before that, it's invalid.

Of course, checking directly in every request is costly and defeats the purpose of jwts, so think carefully if you could just use an opaque token and check against a white list instead of a black list.

 

Thanks, Daniel!
The final solution, of course, depends on the app's needs :) But this was definitely a useful addition! Most likely one should handle all those cases in a fully functional app.

 

But JWT has it's own invalidate() that you can use while logging the user out in the backend

vendor/tymon/jwt-auth/src/JWTManager.php
    /**
     * Invalidate a Token by adding it to the blacklist.
     *
     * @param  Token  $token
     * @return bool
     */
    public function invalidate(Token $token)
    {
        if (! $this->blacklistEnabled) {
            throw new JWTException('You must have the blacklist enabled to invalidate a token.');
        }

        return $this->blacklist->add($this->decode($token));
    }
 

I came to a similar conclusion. If you really must have log out functionality, then you can use a black list. However, using a black list is not a lot different from the old school way of stateful sessions. You still have to lookup the token on every request to be sure it is still valid. So, the blacklist can have a performance impact to the service (or even a bottleneck) just like with session-based auth.

Using refresh tokens could help a little. With them you can implement short-lived auth tokens. For example, if the token expiration is 5 minutes, then you can be sure that a user's permission changes will take 5 mins at most to take effect. However, refresh tokens are considered insecure to keep in the browser, so no help for web apps. (You can do it using HttpOnly cookies, for example, but then getting a new auth token may be visible to the user with redirects.)

And taking refresh tokens to its logical extreme of getting an auth token before every API request... it is no different from looking up a blacklist or looking up an auth session by session identifier. It is still a lookup on every request.

The performance gains come by balancing expiration time with how responsive security changes must be. If security changes must be immediate, then the auth solution becomes stateful and more expensive to scale. No matter which approach you use.

 

Nice article, but I would suggest you use standard HTTP session if you need this functionality.
Because JWT should be stateless.

 

You are right! It's just a lot of people decide to use JWT and then start wondering how to handle logouts :)
I also suggest thinking on what implementation suits the app best and choose JWT only when it's definitely the best matching solution.

 

I'm confused? If we add a blacklist from a db that we have to check, what is the purpose of using JWT at all? Isn't that one of the advantages of have JWT, that you don't need to check the db every time?

 

The answer is that JWTs are misused and often are the wrong tool for the job. Like any new thing, a lot of people jump on the bandwagon without analyzing how and why this works. I suggest this really good article for the ups and downs of JWTs and what are good use cases. It taught me loads about JWTs and also scaling in general.

Being logged in is a state. Being logged out is another state - with substates: missing login information and having the wrong login information. One token can only meaningfully represent two of those states: present and not present. You need another (stateful) medium to represent the third, if that is required. These are technology independant facts.

The good thing about blacklists is that they represent a FAR smaller state than the number of valid sessions, so you can probably keep those cached in memory. This is due to

  • there are far fewer people explicitly logging out than are logged in
  • since all JWTs used as access tokens should have an expiration date you can clean the cache for old entries all the time

So it's not that bad :)

 

Thanks for posting this. I was looking to do the same with JWT.

code of conduct - report abuse