DEV Community

Arpy Vanyan
Arpy Vanyan

Posted on • Originally published at Medium on

How to log out when using JWT

The wonder of JSON Web Tokens

JSON Web Tokens (JWT) is a way of statelessly handling user authentication. What does it mean? Well, JWT helps to organize authentication without storing the authentication state in any storage be it a session or a database. Thus, when checking user’s authentication status you do not need to access the session or perform a database query. Instead, you generate a token based on the user payload of your choice and use it in requests from client side to identify the user on server. 🛂

So, basically, whenever a token is created, it can be used forever, or until it is expired. JWT generator can get an option to invalidate the token after specified time.

But what to do if you want to invalidate an existing token? What you actually need to do when the user opts to log out, or let’s say change password? 🤔

Let’s log out

Okay, so usually, when using JWT authentication, the client side stores the token somewhere and attaches it to every request that needs authentication. So, the first thing to do when logging out, is just delete the token you stored on the client (e.i. browser local storage). In that case the client won’t have a token to put in the request, thus causing unauthorized response status. But is that enough? Well, that specific client (browser, app) won’t be authenticated any more, but the token still exists somewhere and it is still valid! If someone has copied the token from the request before, he/she would still be able to perform requests on behalf of the user! 👾 You can easily try this out on your own.

“Okay, so let’s log out the user from the backend!” you would say. But hold down the horses. It’s not that simple with JWT. You cannot delete the session or cookie and get going.

Actually, JWT serves a different purpose than a session and it is not possible to forcefully delete or invalidate an existing token.

Expiring a token?

Yes, the tokens can be expired. No, you cannot do it on demand.

When signing a user payload for a JWT you are allowed to pass an expiration time to it. You can provide it as a field called exp in the payload like this:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516234022,
  "exp": 1516239022
}
Enter fullscreen mode Exit fullscreen mode

The expiration field takes number of milliseconds since the start of Unix epoch. As the iat field here stands for “issued at”, this token is set to expire 5 seconds after it was issued. ⏰

Note: If you are using one of the JWT libraries listed here, most likely you can also pass an expiration time in the signing method options.

If you don’t want to have forever valid tokens, you should always set a reasonable expiration time on you JWT. The amount of time really depends on your application. We’ll go with one day tokens here and generate them in our login action. For a NodeJS app the code should look something like this:

const jwt = require('jsonwebtoken');

const payload = {
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516234022
}

const token = jwt.sign(payload, 'your-secret', {expiresIn: '1d'})
Enter fullscreen mode Exit fullscreen mode

So, when the token expires, the validator will return an error and you backend will respond with an unauthorized response status as soon as it gets a request that needs authorization. Usually, you will unset the token from the client side and redirect the user to the login page. So, with this example, all users will be automatically logged out after 1 day of using your app.

“Cool, but I still want to log out!” ➡️

As already said, you cannot manually expire a token after it has been created. Thus, you cannot actually log out with JWT on the server side 🙀Or, unless, you can…

It is said that using JWT should be 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 client side, then you might need to neglect the stateless logic and do some queries.

An implementation would probably be, to store a so called “blacklist” of all the tokens that are valid no more and have not expired yet. You can use a DB that has TTL option on documents which would be set to the amount of time left until the token is expired. Redis is a good option for this, that will allow fast in memory access to the list. Then, in a middleware of some kind that runs on every authorized request, you should check if 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.

Conclusion

One does not simply log out with JWT…

As it seems, creating a clean log out flow when using JSON Web Tokens is not so straightforward. You should either let a token be active until it is expired by itself, or opt to use a storage for logged out tokens if you want to restrict the usage of a token when a user logs out. To sum it all up, simply follow this 4 bullet points:

  1. Set a reasonable expiration time on tokens
  2. Delete the stored token from client side upon log out
  3. Have DB of no longer active tokens that still have some time to live
  4. Query provided token against The Blacklist on every authorized request

image credit: cartermatt.com

Top comments (15)

Collapse
 
alainvanhout profile image
Alain Van Hout

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.

Collapse
 
_arpy profile image
Arpy Vanyan

Totally agree :)

Collapse
 
danielescoz profile image
Daniel Escoz

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.

Collapse
 
_arpy profile image
Arpy Vanyan

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.

Collapse
 
kspeakman profile image
Kasey Speakman

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.

Collapse
 
branislavlazic profile image
Branislav Lazic • Edited

Having refresh tokens is again, similar to saving a session. Yes, the traffic is vastly reduced since a refresh token is checked only when your JWT expires. But still, it doesn't provide any advantage over serialized sessions in terms of scalability. In the end, why have such a complex and potentially insecure architecture when you can simply use cookie + session-based authentication?

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

Tracking sessions and CSRF tokens across servers requires extra infrastructure (something like Redis) for scalability, which is far more expensive than using refresh tokens. The OAuth2 protocol is complicated yes. But since it is an open protocol, there are many libraries to help insulate your code from that complication. And you have to look at what it buys you. You can delegate the hard or tedious parts of security (authentication, password storage, forgot password functions, etc.) to a provider. You also avoid complication in your architecture by not depending on the uptime of the session tracking database and cost by not having to pay for its resource usage.

Collapse
 
polc profile image
Paul Le Corre

Great article! That's one of the reasons it's not recommended to use JWT for session.

See cryto.net/~joepie91/blog/2016/06/1...

Collapse
 
_arpy profile image
Arpy Vanyan

Thanks. And yes, it is :)

Collapse
 
ashraf_hefny profile image
Ashraf Hefny

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));
    }
Collapse
 
_arpy profile image
Arpy Vanyan

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.

Collapse
 
toddcoulson profile image
Todd Coulson

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?

Collapse
 
kopseng profile image
Carl-Erik Kopseng

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 :)

Collapse
 
yogeshkhater profile image
Yogesh Khater • Edited

Another solution could be,

  • Store any random salt in user's DB record.
  • While encryption/decryption of the JWT tokens, we can use "your secret" + the salt.
  • On logout, update the salt.

It would make any existing JWT tokens invalid immediately without the need of a blacklist.

Collapse
 
praneetnadkar profile image
Praneet Nadkar

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