JSON Web Tokens (or JWTs) have become incredibly popular and you’ve likely heard of them
before. What you may not have heard is that JWTs were originally designed for use in OAuth – which is
fundamentally different to user sessions.
While the use of JWTs for OAuth is widely accepted, its use for authenticating users sessions
is controversial (see this post). In this article, I will attempt to make a comprehensive list of the pros and cons
of using JWT for this context. I do not intend to solve this debate, since devs (especially devs) are
often strongly opinionated. I only aim to summarize all the perspectives.
However, I do offer my opinion on the best solution for session management (spoiler: it has the advantages of JWTs
without any of its disadvantages!)
The flow of the content is as follows:
A cursory note on session management.
User sessions involve managing tokens across your app’s backend and frontend. These tokens
act as a proxy to your’s identity and can either be:
Non opaque tokens have a special property that enables the backend to verify that the token
is legitimate. This is achieved by cryptographically signing them, and in doing so, we get what is known as a
JWT – a signed, non-opaque token.
A clarification note: I am only concerned with session management between an app’s
backend APIs and frontend. There is no third party service involved (i.e. no OAuth 2.0).
The following is a list of all the pros for using JWTs – aggregated across multiple sources.
These are benchmarked relative to opaque tokens (the only other type of token for sessions). I have also included
some common misconceptions and have labeled them as “myths”:
1)Fact: No database lookups: It’s generally known that for most APIs,
network calls add the most latency. Hence, it’s reasonable to expect that having no network calls (no database
lookups) for session verification is beneficial.
To prove this, I ran a test to see latency times (requests per second or RPS) of APIs that
used JWTs and not. The displayed RPS are an average of running the tests 60 times. Following are the different
APIs that were tested:
For each API, I set up the database / cache in three locations:
1) The same machine (as the API process)
2) A different machine, but within the same WiFi network
3) A different machine with a different network (to the API process), but within the same
city (an AWS EC2 instance). All machines have roughly the same spec in terms of processing power and RAM.
As it can be seen, database lookups are indeed much slower, especially over distributed
machines (which is very often the case). However, there are counters to this point:
2) Myth: Saving on database space: Since JWT’s don’t need
to be stored in the database, it’s true that it does save space. To get a sense of how much, let’s do a back of an
So by using JWTs, we are saving 300 MB of database space per million users. This doesn’t make
much difference since it would cost approximately $0.03 extra per month on AWS as per their pricing.
3) Myth: More secure because it’s signed: The signing of
the JWT token is only required so that clients cannot manipulate the content in the token. Whereas, opaque tokens
cannot be manipulated since the string itself doesn’t have any meaning. Just having a long opaque token (high
entropy) is good enough. Hence, the signing of JWTs doesn’t add any extra security in comparison to opaque tokens,
it simply matches the security level.
4) Myth: JWTs are easier to use: It is true that JWTs are easier to get
started with since we don’t have to take the effort to build a system that reads the database for session
verification, or a cron job to remove expired tokens… However, these are quite easy to implement anyway.
5) Myth: JWTs are more flexible: Flexibility comes because
we can put anything in a JWT. However, we can do the same with opaque tokens. Any data can be stored in the
database against an issued opaque access token.
6) Myth: JWTs automatically prevent CSRF: As long as we are
using cookies for JWT (which is recommended), we also have to take care of CSRF attacks, just like if we use an
opaque token. This attack vector will have to be prevented using anti CSRF tokens or SameSite cookie attribute,
both of which are independent of if we use JWT or opaque tokens.
7) Myth: No need to ask users for ‘cookie consent’: Cookie
consent which is required for GDPR, applies only to cookies used for analytics and tracking. Not for keeping users
logged in securly. JWTs and opaque tokens are the same in regards to this point.
8) Other myths: I have also read people claim that JWTs
work better than opaque tokens for mobile and also work even if cookies are blocked. Both of these are simply not
Overall, it seems that the only advantage of JWT over opaque token is lesser latency in API
requests (which is a major win). Now let’s have a look at the cons.
Like the above section, the following is a list of all the cons that I have thought about,
as well as what I have read from other sources:
1) Fact: Non revocable: Since verifying JWTs doesn’t
require any lookup to a single source of truth (database), revoking them before they expire can be difficult. I
say difficult and not impossible because one can always change the JWT signing key and then all issued JWTs will
be immediately revoked. Revocation is important in many cases:
One solution that people recommend is to use revocation lists. This is where you keep a list
of revoked JWTs and check against that list when verifying the JWT. But if we do this, it’s almost the
same as opaque tokens since we will have to do a database / cache lookup in each API. I say
almost since here, we have the option to choose which APIs should check against the blacklist and which
should not. So this may be an advantage in certain scenarios over opaque tokens.
One more solution is to keep the lifetime of the JWT very small (~10 mins). However, this
also means that users will be logged out every 10 mins. There are various session flows that one can implement to
have short lived JWTs while maintaining a long session as explained in this blog
post. We will be exploring the recommended method later in this post.
2) Fact: Bottlenecked against one secret key: If the
signing key of the JWTs is compromised, then the attacker can use that to change the userId in their JWT to any
other user’s. This allows them to hijack any user’s account in a system. This secret key can be compromised in a
variety of ways like employees making a mistake (by pushing the key to github) or purposely leaking the key.
Attacks to your servers might also leak this key.
A counter to this is that even opaque tokens from the database can be leaked. However, those
are much harder to leak (because of their sheer volume) and cannot be used to compromise new accounts or accounts
that don’t have an active session during the time of attack.
3) Fact: Crypto deprecation: Signing of JWTs requires use of a cryptography
instrument called hashing. It is usually recommended to use SHA256 for this. However, what happens when this gets
deprecated? At that point, one may want to switch to a newer algorithm. While making this change is relatively
straightforward, the problem is that developers are very busy and often will miss out on such deprecations. That
being said, such deprecations are very infrequent.
4) Fact: Monitoring user devices: In the most simple
implementation, if one is using JWTs for their sessions without any session information stored in the database,
their app will not be able to know which devices or how many devices a user is using. This may often cause
business logic and analytics issues. This being said, it’s easy to add some information to the database when a JWT
is issued and remove it once it expires. This way, this disadvantage can be mitigated. However, this is something
that needs to be done purely outside the scope of a JWT (hence this point).
5) Myth: Cookie size is too large: A typical JWT can be 500 bytes long, versus a 36 or 64 bytes sized
opaque token. These are to be sent to the frontend via cookies and these are sent to the backend on each API
request. This causes two problems:
6) Myth: Data in JWT is visible to everyone: First, the priority should be
that JWTs themselves should not be accessible by anyone malicious because then they can gain unauthorised access
to an account (which is a far bigger problem than being able to see the contents of the JWT). However, if that
does happen, one should also refrain from putting any sensitive information in a JWT. Instead, one can store this
information in the database. Either way, this is not a con of using JWTs.
Seeing the pros and the cons above, my opinion is that just using JWTs is probably not worth
it. The risks, I feel, outweigh the benefits. However, what if we could use a different approach where we use
both, opaque tokens and JWTs. Perhaps, this would allow us to eliminate the cons whilst keeping the pros?
Once the user logs in, the backend issues a short lived JWT (access token) and a long lived
opaque token (refresh token). Both of these are sent to the frontend via httpOnly and secure cookies. The JWT is
sent for each API call and is used to verify the session. Once the JWT expires, the frontend uses the opaque token
to get a new JWT and a new opaque token. This is known as rotating refresh tokens. The new JWT is used to make
subsequent API calls and the session continues normally. This flow is illustrated in the diagram below:
Now let’s revisit pros and cons for this new session flow.
1) No database lookups: Since most API calls still use the JWT, this
advantage still holds. We will need to call the database when refreshing the session, but this is a relatively
rare event (relative to the number of session verifications that do not require a database lookup).
2) Added security via session hijacking detection: Using
rotating refresh tokens, we are now able to detect stolen tokens in a reliable way. This will help prevent session
hijacking attacks. Learn more about it here.
We can see that the main advantage of using JWTs still holds, and we have also added a new
1) Partially Solved: Non revocable: We can use short lived
JWTs and long lived refresh tokens to maintain a long session as well as get substantially more control on
revocability. To revoke a session, we must now simply remove the opaque token from the database. This way, when
the refresh API is called, we can detect that the session has expired and log out the user. Note that this will
not immediately revoke a session – it depends on the lifetime of the JWT. But it makes this problem much more
2) Solved: Bottlenecked against one secret key: We can
keep changing the JWT signing key every fixed interval of time. When the key is changed, all current JWTs will be
immediately invalidated. In this event, the frontend can simply use its refresh token to get a new JWT (and a new
refresh token) signed with the new key. This way, we can vastly minimise our dependency on this secret key.
3) Not solved: Crypto deprecation: This point is still a
problem, however, changing the hashing algorithm can be done smoothly and immediately just like how we change the
4) Solved: Monitoring user devices: Since we have an
opaque token for each session, we can easily monitor the devices each user has.
We can see that most of the cons have been roughly resolved and are now all acceptable
My opinion is that using JWTs, especially for long lived sessions, is not a good idea. Using
short lived JWTs with long lived opaque (refresh) tokens in the following scenarios:
The need for easier scalability is higher than the need for
immediate token revocation. The final decision depends on your use case. Do you expect your
app to scale to millions of users (JWTs preferable) or less than tens of thousands (opaque
tokens preferable)? How important is instant revocation? If you ban a user or detect theft,
the token will continue to be valid till it expires (let’s say 30 minutes).
The 4k bytes cookie size is not a limiting factor.
You do not need to map “sensitive” information to the access token that is required in
each API call.
When I think of consumer apps that I want to develop, most of them meet the criteria above.
I feel that it is a perfect balance between scalability and security. For all other requirements, stick to short
lived opaque access tokens and long lived opaque refresh tokens.
Note that we have not spoken about the applicability of JWTs for OAuth and have only focused
on sessions between an app’s backend API and frontend. JWTs are generally an excellent use case for delegation of
access to third party services (OAuth). In fact, they were originally designed for this exact purpose.
If you like the session flow I described, please checkout SuperTokens. It is a robust solution that has
implemented rotating refresh tokens with JWTs (and opaque tokens coming out soon). It provides all the benefits
mentioned above and also prevents all session related attacks.
Written by the Folks at SuperTokens — hope you enjoyed! We are always available on our Discord server.
Join us if you have any questions or need any help.
This is a total of 315 bytes. The JWT header is normally between 36 and 50 bytes and finally
the signature is between 43 and 64 bytes. So this gives us a maximum of 429 bytes which would take about 10% of