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
}
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'})
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
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:
- Set a reasonable expiration time on tokens
- Delete the stored token from client side upon log out
- Have DB of no longer active tokens that still have some time to live
- Query provided token against The Blacklist on every authorized request
Top comments (15)
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.
Totally agree :)
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:
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.
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.
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?
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.
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...
Thanks. And yes, it is :)
But JWT has it's own invalidate() that you can use while logging the user out in the backend
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
So it's not that bad :)
Another solution could be,
"your secret"
+ the salt.It would make any existing JWT tokens invalid immediately without the need of a blacklist.
Thanks for posting this. I was looking to do the same with JWT.