JWT - Reinventing Sessions

I wish I could come up with a clearer diagram on why using JWT is not the most practical idea, but ultimately this is the best flow graph.  Credit to the author of this blog.  

Before jumping into the technical details of this blog post I am going to define some common terms that will be used throughout.

  • 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.

JWT vs Cookies

Many developers commonly discuss "cookies vs. JWT".  However, this comparison is the same as comparing apples and oranges.  Cookies are used as 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".  Therefore, in this blog post we will be comparing sessions to JWT tokens (hence the title).  

JWT Advantages

When people recommend JWT, they usually claim one or more of the following benefits:

  • Easier to Scale
  • Easier to Use
  • More Secure
  • Built-in Expiration Functionality
  • Prevents CSRF

Using other public blogging posts, and independent research this blog post will discuss each of these individual advantages, and why they might not be correct.  

Easier to scale

According to Sven Slootweg, joepie91, the ability to scale might be the only advantage that is somewhat true, but only if you are using stateless JWT tokens. However, most web application do not need this kind of scalability.  There are easier ways to scale up, and unless you are operating at Alexa Top 100 bandwidth limits there is no need for 'stateless sessions'.  I know what you are thinking, what if my site becomes one of the Alexa Top 100 sites in the next month or so.  It is fairly easy to replace the session mechanism at a later point, with the only cost being logging out every user one time.

Easier to use

You will have to deal with session management yourself, on both the client and the server side, whereas standard session cookies have been throughly documented and just work, out of the box.  JWT has not been developed extensively to allow easy deployment.

More secure

There is a common misconception that JWT tokens are more secure because they are using cryptography.  While signed cookies are more secure than unsigned cookies, this is not exclusive to JWT tokens.  Thoroughly battle tested session implementations use signed cookies as well.  Using cryptography inside payload does not make the configuration correct.  In fact, incorrectly used cryptography can make something less secure.  

Another argument is that JWT is not sent as a cookie.  Which is counter-productive as cookies are well-protected, for example against malicious client-side code.  If the security concern is an attacker intercepting your session cookie, then implementing TLS with the latest patches and secure configurations will prevent further snooping.

Built-in expiration functionality

Yes, JWT tokens do in fact have expirations.  However, many times when observing the tokens expiration times are usually much too long for secure sessions.  As well expiration can be implemented server-side just as well, and most session management solutions come with expirations built-in.  In fact, keeping server-side session management is the more preferable of the two methods since you can clean up session data that is not needed anymore, which cannot be done if you use stateful JWT tokens that have not expired yet.  

Prevents CSRF

Unfortunately, JWT does not really prevent CSRF.  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.
  • Local Storage: Now you are not vulnerable to CSRF attacks, but your application or site now requires JavaScript to work, and you've just made yourself vulnerable to an entirely different, potentially worse class of vulnerabilities.

The only correct CSRF mitigation is a CSRF token. The session mechanism is not relevant here.

Still Not Convinced?

Now that we have covered all the common claims, and spoke briefly about the refutes of those claims.  These refutes might not seem serious to you, and are still considering JWT tokens.  There are still a few disadvantages of using JWT as a session mechanisms, some of which are potential security issues.

JWT Storage Space

JWT tokens are not small in size, especially when using stateless JWT tokens, where all the data is encoded direcrtly into the token.  This can cause size limits of a cookie or URL.  This can cause the developer to utiilze Local Storage methods.  Take into consideration, 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.Local storage, unlike cookies, doesn’t send the contents of your data store with every single request. The only way to retrieve data out of local storage is by using JavaScript, which means any attacker supplied JavaScript that passes the Content Security Policy can access and exfiltrate it. Not only that, but JavaScript also doesn’t care or track whether or not the data is sent over HTTPS. As far as JavaScript is concerned, it’s just data and the browser will operate on it like it would any other data.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

Unlike sessions, which can be invalidated by the server whenever it needs to cleanup or performing a security incident cleanup, individual stateless JWT tokens cannot be invalidated.  JWT by design, will be valid until they expire.  This means that you cannot, for example, invalidate the session of an attacker after detecting a compromise without also invalidating all other legitimate users.  Considering this design flaw you can also not invalidate a session if a user were to change their password.  Which should be seen as a legitimate issue.  

I know you are thinking, what about a whitelist/blacklist server that can manage the different validity of the different JWT tokens?  You are essentially building a complex stateful infrastructure to explicitly detect and reject them.  Which defeats the entire point of using stateless JWT tokens to begin with.  

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.  

Conclusion

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 use cases for JWT, but that they're just not suitable as a session mechanism. This still holds true; the use cases where JWT is particularly effective are typically use cases 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.  Until next time!  Keep on learning!

Many of the ideas and research originated from this blog.  I wanted to take the blog post, learn from it, and add it to my blog to further reach a broader audience.