top | item 33019960

Why to not use JWT (2021)

157 points| thunderbong | 3 years ago |apibakery.com | reply

246 comments

order
[+] pwinnski|3 years ago|reply
> An old joke is that there are only two hard things in computer science: cache validation, and naming things.

Way to ruin the joke! The joke is that that are only two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

See, now it's a joke.

[+] wk_end|3 years ago|reply
As someone who's a little behind the times here, can someone explain the distinction between a session ID and a token? I'm referring to this passage:

> Sessions were built on top of cookies. The server kept context about the user's "session" [...] and indexed it with a random session ID. The session ID was sent as a cookie and for each subsequent request, server looked up the session state.

> Then came mobile apps and single-page JavaScript apps, and plain cookies and sessions started being inadequate. Developers started using tokens, where a token was an unguessable random string that behaved like an identity card. Possession of it proved your (client's) identity (bearer token).

Both a session ID and a token are random strings clients send with requests to indicate who they are. Why were sessions inadequate, and how do tokens solve this problem?

[+] slowmovintarget|3 years ago|reply
If you have a "true microservices architecture" as the article comments, you have a time budget for every request from the front end, through the graph of calls on the back-end.

Let's imagine a call-graph budget of 300ms. Every hop after the request lands on the back end takes a bite of that. Let's imagine a straight stack of calls three deep with no branching. Front-end makes a RESTful API call to Service A. Service A calls B, B calls C, and C queries a database.

Diving down to the bottom, the database call + networking will take 50ms (it's a big query), service C spends 5ms filtering, and another 10ms reorganizing the data into its response format and returns. 65ms eaten.

Service B deserializes (3ms) and acts on the data (10ms), then serializes the response (2ms). 80ms eaten.

Service A gets this back and chews on it for a bit to put it into shape for the front end. There's also a layer of proxies and caches the request and response goes through, so another 20 ms here. 100ms! Great! We're under budget (because we're not counting network latency back to the UI)!

Now imagine, that for every one of these hops, we add a 10ms call to a session service, and processing time (hopefully tiny) to review, and error handling. We've also only covered happy path, we've not considered call retries, branching calls to resolve additional data, each with a +10ms check for that session.

Now that time budget is starting to chafe. Validation of signed tokens with claims eliminates that 10ms tax on every call, and it is a big deal.

How do I know? I work on a system with hundreds of microservices. We use JWTs for back-end authentication. Thank goodness the article clears us to use JWTs. :)

[+] ehutch79|3 years ago|reply
There's nothing actually wrong with session ids in a cookie. It's that JWT was the new hotness and a buzzword. Now there's a lot of cargo culting around them.

JWTs are a signed claim that your service can trust, and use for authorization after just verifying the signature.

A session id, you need to take the id and look up your session in some mechanism on your backend.

JWTs are great for different services saying 'yes, this connection is actually this person, trust them.' Inter-server communication, oauth, etc, are good examples.

Session ids only work for the issuing service. Django sessions, php sessions, etc, are good examples.

One of the biggest problems with JWTs is that a lot of people are ONLY storing a user_id, or a session_id in them. On the server the decode the token and proceed with the standard session_id workflow, still hitting the db.

Invalidation is another thing. You still end up hitting the database, and if the JWT is only ever used by your own service, why bother?

Then there's people just shooting themselves in the foot and not actually signing them and just trusting them without any crypto signatures.

[+] WA|3 years ago|reply
Session ID: Server has a list of valid sessions. Compares your session ID to its own list. Have session token? Access granted.

JWT: Server doesn't keep a list. Someone comes with a JWT, which is signed by the server. Server grants access purely on the fact that there is a signed token.

JWTs are useful if you don't want to sync the server-side list of valid session ids (many microservices for example).

[+] bradstewart|3 years ago|reply
It's a question of cookies vs HTTP headers. Cookies are hard to use with CORS (when using backend-as-a-service stuff like Firebase, in particular) and/or mobile apps.

There's really no difference between a random session id and a random token. But people typically think "session id == cookie" and "token == header".

More secure session cookies are also HTTP-only, which means JavaScript code can't access them. Which can be a security feature in a lot of cases, but makes SPAs harder.

[+] tyingq|3 years ago|reply
I think they are trying to point out that a "session ID" is typically a key into session data stored on the server side, where the token is usually not just a key, but all the session data stored client side. With signing and other mitigations intended to keep that from being dangerous.

>Why were sessions inadequate

I suspect there are more reasons, but one is likely CORS and the tendency for the auth infra to be separate from the app infra.

[+] stefs|3 years ago|reply
note: most other answers compare sessions to JWTs, not the bearer token. i'm guessing you're asking about sessions vs. bearer tokens, not sessions vs. JWT.

----

i've been wondering the same thing. the only explanation i have are life times and explicitness. sessions are usually based on the browser session, i.e. cookies without an explicit expiration date are invalidated if the browser is closed. also, session cookies are always sent on any request (simplification) to the matching hosts. this means you have to re-login to get a fresh session every time you reopen the browser (unless you set an expiration date for the session cookie i guess).

i guess bearer tokens have a very long life time independent from the browser session and the user can invalidate them manually through some settings option. moreover, the token is added to the relevant backend calls manually (instead of automatically by the browser).

i'm not sure though how a bearer token would prevent CSRF. imo it's roughly the same as a session with a long expiration time.

[+] 9dev|3 years ago|reply
I think the easiest explanation is this:

Session IDs are bound to cookies from a given origin. Thus, every request needs to include the original cookie, and have the same origin, to transport authentication data. But cookies cannot be accessed from JavaScript, only be sent implicitly if these things are true.

This made it difficult to work with separate hosts (www.my.app vs. api.my.app).

In contrast, Bearer tokens are explicit: JavaScript code can use and attach them to requests as appropriate, thus allowing for more control. The also are a little easier to work with (no reason to keep a cookie jar, stateless stuff works).

JWTs in turn aren't simply random strings anymore -- they contain some claims about the bearer, which are cryptographically signed, so whoever receives the token can be sure the claims haven't been tampered with. This allows for very powerful, interconnected services -- but most people use them to pass a session ID around, so... here we are :)

[+] jmartrican|3 years ago|reply
The token refers to an API token or an OAuth token. These tokens are what backend systems require to allow a user to access data.

The problem is that these tokens should not be used by your JS code (they should never make it to the browser for security reasons). So how exactly does a user access their data if the front-end code running on their browser does not know the token? That's where sessions come in. The webapp hosting the front-end JS code (for example Node.js or Java-Spring) creates a session whenever a request is made. When the user logs in, their token (which the server retrieves though whatever authentication method is being used) is stored in the webapp as session data. That is to say, for each session, the webapp can store information about that session.... e.g. the token. The token is then used by the webapp to access the user's data via the API calls to the backend. The backend, will then use the token to verify the user has access to the requested data (or the requested resources).

[+] 0x457|3 years ago|reply
I think it came from misunderstanding a lot of things by people making SPAs...

Session cookie is invisible to JavaScript, so handling unauthenticated state is a bit weird.

Bearer token is visible to JavaScript, so SPA can make some assumption about authentication status. However, it still must handle 401s because token could have been revoked or expired (JWT solves the expired problem). You also could encode some metadata like username and email and instead of having a single round-trip to fetch it once, now you will be sending kilobytes of cookies with every request.

CORS is a black magic to them. How to properly set cookie is also back magic - who would know that you can set cookie in way that would include subdomains, so it will be sent to www.acme.org AND to api.acme.org.

It was also weird to set a session cookie on outgoing requests when service a talks to service b.

[+] LoganDark|3 years ago|reply
Sessions are a unique identifier that index into some server-stored data, which requires the server to store that data. Tokens include the data itself (along with a signature that verifies it's actually from the server). For example, Discord bot tokens include the user ID of the bot they're associated with. So do user tokens.

With sessions, you have to keep track of every client that may be used to access an account. With tokens, those clients self-identify. You have to store less.

Some services unify multiple clients into one session but this is uncommon in my experience.

[+] ilovetux|3 years ago|reply
The way I see it is that session ids are handed out by the server as you login and revoked when you log out, so while it would be advisable for this session ids to be truly random there is no guarantee that this is the case. In truth I have seen sessions stored with an autoincrement id meaning that if you were to get a session id you could, in theory, just use the number before yours and highjack someone else's session.

A bearer token, in my experience, is a cryptographically generated random key (presumably unguessable) which the user can create, use and revoke at will.

[+] adolph|3 years ago|reply
> Both a session ID and a token are random strings clients send with requests to indicate who they are. Why were sessions inadequate, and how do tokens solve this problem?

JWT tokens are not random strings. They contain claims about the bearer that are cryptographically signed to prevent the bearer from altering them.

https://jwt.io/introduction

[+] GoToRO|3 years ago|reply
Session IDs were short, so it was easy to randomly guess them. You would get access to some random account.

Also randomly generating them would result in colisions if you had a lot of traffic.

[+] quesera|3 years ago|reply
I have happily used JWTs as signed objects that can be easily parsed and validated by any API consumer, in any language, using library code.

These objects can be transferred through untrusted channels, and retain their verifiability. That can be useful when moving data between organizations, or systems that do not talk directly (e.g. a mobile app that interacts with multiple unrelated backends).

RFC 7519 says:

  JSON Web Token (JWT) is a compact, URL-safe means of representing
  claims to be transferred between two parties.
Not all claims are authorization claims. Not all claims require ad hoc invalidation. Some claims can even be permanent!

Authors ad infinitum have pointed out the risks in using JWTs for authorization (linked web article says "authentication", but both could apply).

Those risks are:

  - Invalidation/expiration controls are limited
  - Receiver might not properly validate signature
  - Protocol dumbly allows "none" algorithm
Only the first is an operational concern, the others are just "bad code works badly" problems.

The moral of the story is: If your claims do not fit the timed-expiration model of JWTs (e.g. some authorization claims), then don't use JWTs!

[+] drinchev|3 years ago|reply
What I'm pissed about is that everyone hopped on the JWT train too fast a couple of years ago.

Now it's really hard to argue with architects / developers why cookie authentication / bearer token makes more sense than JWTs.

[+] cogman10|3 years ago|reply
> Now it's really hard to argue with architects / developers why cookie authentication / bearer token makes more sense than JWTs.

Because that's a nonsensical argument? JWT is just a token + validation. Nothing more. You can use JWTs in cookie authentication, you can use them as bearer tokens. The only thing JWTs are doing is carrying a payload and signing it.

Now, if you want to talk about Oath2 or OIDC then maybe there's a different argument to be had.

[+] solatic|3 years ago|reply
Yep. Not everybody runs at FAANG scale.

Have a B2B product where you'll have, maybe, someday, and I'm exaggerating here, X00,000 DAUs? Just set up cookie auth and track the sessions in Redis. Session revocation is super-super simple and you'll easily be able to handle any security vendor questionnaire asking how you lock out terminated accounts.

[+] ravenstine|3 years ago|reply
JWTs aren't even hard to argue against in terms of logic. What is hard to argue against are developers whose theology includes JWT.
[+] jmartrican|3 years ago|reply
I agree. Not enough critical thinking was happening when I saw devs start adopting JWT without clearly stating why, other than "current best practices is to use JWT... end of discussion".

My concerns with JWT from early on is that the data stored in them was potentially stale. Front-end developers would always request fresh data at each interaction. Second, the JWTs were so long. We had to keep passing these long JWTs around.... mainly for testing stuff out, we had long lived tokens, especially in dev, so I think we passed them around to replicate API calls. So you felt how long they were.... and in my head I kept thinking about all this useless data being passed around taking up CPU/network/memory resources. So I would just remove JWT and replace the tokens with UUIDs. Everyone was happy about it, but they were confused as to why they were needed in the first place. I would just respond with, well when you find out let me know and I can add them back.

[+] super256|3 years ago|reply
How would you handle user authentication on a serverless platform? JWT are imo perfect for that since they are stateless.
[+] eagsalazar2|3 years ago|reply
Hallelujah! I've been saying this for years and people looked at me like I was insane. JWTs are one of the biggest cult/hipster scams of the last few years.

GraphQL everywhere is another one (like JWTs, it has its uses but people went way overboard) and Tailwind is the next one.

[+] brunojppb|3 years ago|reply
This is interesting because one thing people ask me when a system is using JWT is in case they reset their password, how can they invalidate all existing sessions? They basically can’t.

Using A cookie-based/token-based session strategy, you can do that quite easily.

[+] cogman10|3 years ago|reply
JWT is a token, often stored as a cookie. I don't see how it's impossible to invalidate a JWT but easy to invalidate a cookie or token.
[+] chrisshroba|3 years ago|reply
It seems like most of the commenters here are blindly choosing a side when the real answer is, it depends on your priorities. If you want statelessness and one fewer db calls at the expense of timely revocations, pick JWT’s. If you want instant revocation, then accept that you need to check a database every time and don’t use JWT’s. Both options are correct.

As an aside, if you do pick JWT’s, you could create a revocation queue, publish all revocations to that queue, and read that queue asynchronously in every server to keep a token blocklist. You lose statelessness but retain speed (no db call).

[+] Ocha|3 years ago|reply
I think this article is shortsighted: what about not needing to store token at all? I think that is one of the biggest strength to it - you can issue short lived tokens in high volume without needing to store anything anywhere.
[+] nfw2|3 years ago|reply
Not super-opinionated on this, but let me try to steelman the JWT case.

Yes, to perform session invalidation with JWT requires some amount of centralized state, so a perfectly-decentralized auth system is not actually possible.

However, using JWT means this centralized state is much less heavy than traditional sessions. Instead of saving ALL sessions and ALL session data on the server, JWT only requires saving a small fraction of session ids (those that have been recently invalidated) and NO session data. By using JWT, you have simplified your memory needs and reduced them by multiple orders of magnitude.

[+] drikerf|3 years ago|reply
Not saying you should use JWT, but you can invalidate tokens by adding a timestamp to the payload. This way you could invalidate those issued before a certain time and logout users.
[+] ehutch79|3 years ago|reply
The overwhelming majority of JWTs have an expires field. That's not the issue.

The idea is that you have a disgruntled employee with a token that expires in 5-10 minutes. You don't have invalidation checks, so what damage can that employee do in 5-10 minutes? Keep in mind for many companies, exporting a client list is a big deal.

Why would you not have validation checks? The point of a JWT is they're stateless. When you get one, if you have the key, you can validate it without access to the auth service.

If you take your JWT, and then are using the claims to check the database, it is 100% functionally the same as a session token.

JWTs are not something you issue to use on your own service. It's for another service to verify the user on their service based on a factor coming from your service.(yes i'm aware there are other uses)

If the only thing in your payload is a session_id or a user_id. Stop using JWTs.

[+] lucasyvas|3 years ago|reply
This is unnecessary - expiry time is a first-class capability of a JWT and records the time of expiry, not when it was created.
[+] Salgat|3 years ago|reply
The only argument they elaborate on is still easily addressed with a validation of the token against a database on every request, and it's hardly a drawback compared to other methods if the other methods do the same thing.
[+] themenomen|3 years ago|reply
Can't you keep a whitelist/blacklist of tokens in a memory cache like redis/memcached and go from there? As far as I know that is the standard practice to invalidate non expired sessions tokens.
[+] jaimehrubiks|3 years ago|reply
Yes but if each microservice needs to check a cache on each request, then why use jwt at all, you could just save a classic session (with random token) in that cache as well
[+] drinchev|3 years ago|reply
As explained in the article, if that's the case then you can't really trust the JWT anymore only for it's cryptographic signature and you rely on an internal store entry that makes the token valid / invalid.

This makes no benefits as to bearer token or any random string that the server "knows" is a valid authenticated request via internal store, like a DB.

[+] brunojppb|3 years ago|reply
But then you introduce a database-like dependency that potentially every micro service will need access to.
[+] rkagerer|3 years ago|reply
That's discussed in the article in the second paragraph under "Problems with JWT". If you're keeping a cache at each server node then you might as well just use bearer tokens.
[+] eli|3 years ago|reply
why not just use a session token at that point?
[+] newbieuser|3 years ago|reply
why is a blog post that directly praises its own product for short reasons on the homepage?
[+] kerblang|3 years ago|reply
The JWT is irrevocable by design, which is why it's time-boxed to a few minutes and not a few days. The refresh token is meant to be revocable. You the provider would store that in a database and remove it or mark it invalid when you need to shut the user out. So no, it is not simply moving the original problem around without solving it.

Most software devs using cookies would likely struggle to revoke an individual cookie - you can certainly architect that in, but if you're like most of us, nobody thought about it early on and now it's kinda problematic to retrofit. Same reasoning: "Well, it will expire on its own soon enough." JWT's actually make it easier to guarantee expiration since it's part of the token itself instead of being reliant on server-side mechanisms.

It's definitely a pain in the butt to juggle refresh tokens & access tokens as a client-side dev, and almost everyone's API docs are ambiguous and confusing about token lifetimes (refresh tokens often have lifetimes too). But that's another issue.

[+] P5fRxh5kUvp2th|3 years ago|reply
refresh tokens are a workaround for a problem that wouldn't exist if you chose not to use a JWT.
[+] vivekv|3 years ago|reply
I don't quite understand the need for access and session token as a mechanism of expiring sessions. Can we create a signed JWT with the expiration in the body itself? Since it is signed the expiry cannot be tampered with.
[+] oautholaf|3 years ago|reply
In my experience, a working strategy for handling signout or revocation for statically verifyable tokens like JWT is straightforward:

- Clear client side state where you can. - Write signed out/expired tokens to something with a cheap heavy read/eventual consistency model - Fail to signed in if unavailable - Acknowledge that you are gaining latency/availability/ lower costs by trading some precision

I am aware of a very large website most folks use every day that did this for more than a decade and it worked fine.

[+] jdthedisciple|3 years ago|reply
great idea! I'm using JWT in one of my projects and still unsure how to fix the irrevocability of JWT while keeping them stateless. But this seems like a nice intermediate solution
[+] jdthedisciple|3 years ago|reply
Lack of invalidation ("out of the box") is indeed a weakness of JWT.

However, I think in some scenarios it doesn't matter. For example if all the user data is encrypted, an attacker can retrieve it from the server using the JWT but not decrypt it - how useless!

Besides, when it comes to criticisms like this, I would gladly like to be pointed to real-life incidents where JWT as the security-bottleneck was fatally exploited.

[+] highhedgehog|3 years ago|reply
As an exercise I started experimenting with JWT after a colleague told me how great they were.

So i tried to build a simple CRUD webside with authentication using JWT just to practice. The section "Problems with JWT" perfectly explains the issues I had. I think a way too many people just pick up the next random fad and without thinking they start using it.

[+] anonymous344|3 years ago|reply
Great article. I was going to start finally using jwt:s but this helps me not to. Currently using sessions (fast & easy) and if not found, then cookie&db-login. Just after migrating the server found out that saving the sessions manually to database was great. That allowed all users to stay logged in while the server changed.