Update - June 19, 2016: A lot of people have been suggesting the same "solutions" to the problems below, but none of them are practical. I've published a new post with a slightly sarcastic flowchart - please have a look at it before suggesting a solution.
Unfortunately, lately I've seen more and more people recommending to use JWT (JSON Web Tokens) for managing user sessions in their web applications. This is a terrible, terrible idea, and in this post, I'll explain why.
Just to prevent any confusion, I'll define a few terms first:
- Stateless JWT: A JWT token that contains the session data, encoded directly into the token.
- Stateful JWT: A JWT token that contains just a reference or ID for the session. The session data is stored server-side.
- Session token/cookie: A standard (optionally signed) session ID, like web frameworks have been using for a long time. The session data is stored server-side.
To be clear: This article does not argue that you should never use JWT - just that it isn't suitable as a session mechanism, and that it is dangerous to use it like that. Valid usecases do exist for them, in other areas. At the end of this article, I'll briefly go into those other usecases.
A note upfront
A lot of people mistakenly try to compare "cookies vs. JWT". This comparison makes no sense at all, and it's comparing apples to oranges - cookies are a storage mechanism, whereas JWT tokens are cryptographically signed tokens.
They aren't opposites - rather, they can be used either together or independently. The correct comparisons are "sessions vs. JWT" and "cookies vs. Local Storage".
In this particular article, I will be comparing sessions to JWT tokens, and occasionally go into "cookies vs. Local Storage" as well where it makes sense to do so.
Claimed advantages of JWT
When people recommend JWT, they usually claim one or more of the following benefits:
- Easier to (horizontally) scale
- Easier to use
- More flexible
- More secure
- Built-in expiration functionality
- No need to ask users for 'cookie consent'
- Prevents CSRF
- Works better on mobile
- Works for users that block cookies
I'll address each of these claims - and why they are wrong or misleading - individually. Some of the explanations below may be a little vague; that's primarily because the claims themselves are vague. I'll happily update it to address more specific claims; you can find my contact details at the bottom of this article.
Easier to (horizontally) scale
This is the only claim in the list that is technically somewhat true, but only if you are using stateless JWT tokens. The reality, however, is that almost nobody actually needs this kind of scalability - there are many easier ways to scale up, and unless you are operating at the size of Reddit, you will not need 'stateless sessions'.
Some examples of scaling stateful sessions:
- Once you run multiple backend processes on a server: A Redis daemon (on that server) for session storage.
- Once you run on multiple servers: A dedicated server running Redis just for session storage.
- Once you run on multiple servers, in multiple clusters: Sticky sessions.
These are all scenarios that are well-supported by existing software. Your application is very unlikely to ever go beyond the second step.
Perhaps you're thinking that you should "future-proof" your application, in case you do ever scale up beyond that. In practice, however, it's fairly trivial to replace the session mechanism at a later point, with the only cost being logging out every user once, when you make the transition. It's just not worth it to implement JWT upfront, especially considering the downsides that I'll get to later.
Easier to use
They really aren't. You will have to deal with session management yourself, on both the client and the server side, whereas standard session cookies just work, out of the box. JWT isn't easier in any way.
I have yet to see somebody actually explain how JWT is more flexible. Almost every major session implementation lets you store arbitrary data for the session anyway, and this is no different from how JWT works. As far as I can tell, this is just used as a buzzword. If you disagree, feel free to contact me with examples.
A lot of people think that JWT tokens are "more secure" because they use cryptography. While signed cookies are more secure than unsigned cookies, this is in no way unique to JWT, and good session implementations use signed cookies as well.
"It uses cryptography" doesn't magically make something more secure either; it must serve a specific purpose, and be an effective solution for that specific purpose. Incorrectly used cryptography can, in fact, make something less secure.
Another explanation of the "more secure" argument that I hear a lot, is that "they are not sent as a cookie". This makes absolutely no sense - a cookie is just a HTTP header, and there's nothing insecure about using cookies. In fact, cookies are especially well-protected against eg. malicious client-side code, something I'll get into later.
If you are concerned about somebody intercepting your session cookie, you should just be using TLS instead - any kind of session implementation will be interceptable if you don't use TLS, including JWT.
Built-in expiration functionality
This is nonsense, and not a useful feature. Expiration can be implemented server-side just as well, and many implementations do. Server-side expiration is preferable, in fact - it allows your application to clean up session data that it doesn't need anymore, something you can't do if you use stateful JWT tokens and rely on their expiration mechanism.
No need to ask users for 'cookie consent'
Completely wrong. There's no such thing as a "cookie law" - the various laws concerning cookies actually cover any kind of persistent identifier that isn't strictly necessary for the functioning of the service. Any session mechanism you can think of will be covered by this.
In a nutshell:
- If you are using a session or token for functional purposes (eg. keeping a user logged in), then you don't need to ask for user consent, regardless of how you store that session.
- If you are using a session or token for other purposes (eg. analytics or tracking), then you do need to ask for user consent, regardless of how you store that session.
It doesn't, really. There are roughly two ways to store a JWT:
- In a cookie: Now you are still vulnerable to CSRF attacks, and still need protection against it.
The only correct CSRF mitigation is a CSRF token. The session mechanism is not relevant here.
Works better on mobile
Nonsense. Every mobile browser still in use supports cookies, and thus sessions. The same goes for every major mobile development framework, and any serious HTTP library. This is just not a problem at all.
Works for users that block cookies
Unlikely. Users don't just block cookies, they typically block all means of persistence. That includes Local Storage, and any other storage mechanism that would allow you to persist a session (with or without using JWT). Whether you use JWT simply doesn't matter here, it's an entirely separate problem - and trying to get authentication to work without cookies is a bit of a lost cause.
On top of that, users that block all cookies typically understand that this will break authentication functionality for them, and individually unblock cookies for sites where they care about this. It's simply not a problem that you, as a web developer, need to be solving; a much better solution is to explain to your users why your site requires cookies to work.
Now that I've covered all the common claims and why they're wrong, you might think "oh, that's not a big deal, it still doesn't matter that I use JWT even if it doesn't help me", and you'd be wrong. There are quite a few downsides to using JWT as a session mechanism, several of them being serious security issues.
They take up more space
JWT tokens are not exactly small. Especially when using stateless JWT tokens, where all the data is encoded directly into the token, you will quickly exceed the size limit of a cookie or URL. You might decide to store them in Local Storage instead - however...
They are less secure
When storing your JWT in a cookie, it's no different from any other session identifier. But when you're storing your JWT elsewhere, you are now vulnerable to a new class of attacks, described in this article (specifically, the "Storing sessions" section):
We pick up where we left off: back at local storage, an awesome HTML5 addition that adds a key/value store to browsers and cookies. So should we store JWTs in local storage? It might make sense given the size that these tokens can reach. Cookies typically top out somewhere around 4k of storage. For a large-sized token, a cookie might be out of the question and local storage would be the obvious solution. However, local storage doesn’t provide any of the same security mechanisms that cookies do.
After all the trouble those engineers went through to make sure nobody is going to make off with our cookie jar, here we are trying to ignore all the fancy tricks they’ve given us. That seems a little backwards to me.
Simply put, using cookies is not optional, regardless of whether you use JWT or not.
You cannot invalidate individual JWT tokens
And there are more security problems. Unlike sessions - which can be invalidated by the server whenever it feels like it - individual stateless JWT tokens cannot be invalidated. By design, they will be valid until they expire, no matter what happens. This means that you cannot, for example, invalidate the session of an attacker after detecting a compromise. You also cannot invalidate old sessions when a user changes their password.
You are essentially powerless, and cannot 'kill' a session without building complex (and stateful!) infrastructure to explicitly detect and reject them, defeating the entire point of using stateless JWT tokens to begin with.
Data goes stale
Somewhat related to this issue, and yet another potential security issue. Like in a cache, the data in a stateless token will eventually 'go stale', and no longer reflect the latest version of the data in your database.
This can mean that a token contains some outdated information like an old website URL that somebody changed in their profile - but more seriously, it can also mean somebody has a token with a role of
admin, even though you've just revoked their
admin role. Because you can't invalidate tokens either, there's no way for you to remove their administrator access, short of shutting down the entire system.
Implementations are less battle-tested or non-existent
You might think that all these issues are just with stateless JWT tokens, and you'd be mostly right. However, using a stateful token is basically equivalent to a regular session cookie... but without the battle-tested implementations.
Existing session implementations (eg.
express-session for Express) have been running in production for many, many years, and their security has been improved a lot because of that. You don't get those benefits when using JWT tokens as makeshift session cookies - you will either have to roll your own implementation (and most likely introduce vulnerabilities in the process), or use a third-party implementation that hasn't seen much real-world use.
Stateless JWT tokens cannot be invalidated or updated, and will introduce either size issues or security issues depending on where you store them. Stateful JWT tokens are functionally the same as session cookies, but without the battle-tested and well-reviewed implementations or client support.
Unless you work on a Reddit-scale application, there's no reason to be using JWT tokens as a session mechanism. Just use sessions.
So... what is JWT good for, then?
At the start of this article, I said that there are good usecases for JWT, but that they're just not suitable as a session mechanism. This still holds true; the usecases where JWT is particularly effective are typically usecases where they are used as a single-use authorization token.
From the JSON Web Token specification:
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. [...] enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
In this context, "claim" can be something like a 'command', a one-time authorization, or basically any other scenario that you can word as:
Hello Server B, Server A told me that I could <claim goes here>, and here's the (cryptographic) proof.
For example, you might run a file-hosting service where the user has to authenticate to download their files, but the files themselves are served by a separate, stateless "download server". In this case, you might want to have your application server (Server A) issue single-use "download tokens", that the client can then use to download the file from a download server (Server B).
When using JWT in this manner, there are a few specific properties:
- The tokens are short-lived. They only need to be valid for a few minutes, to allow a client to initiate the download.
- The token is only expected to be used once. The application server would issue a new token for every download, so any one token is just used to request a file once, and then thrown away. There's no persistent state, at all.
- The application server still uses sessions. It's just the download server that uses tokens to authorize individual downloads, because it doesn't need persistent state.
As you can see here, it's completely reasonable to combine sessions and JWT tokens - they each have their own purpose, and sometimes you need both. Just don't use JWT for persistent, long-lived data.