Hacker News new | past | comments | ask | show | jobs | submit login
Why JWTs Suck as Session Tokens (2017) (okta.com)
91 points by powvans on Oct 3, 2022 | hide | past | favorite | 84 comments



Yes for basic websites simple DB based session might be enough. And if you think about these sessions they are essentially opaque and stateful tokens pointing to a row in DB containing the information you need. But saying JWT sucks in plain generic terms is out of context statement, in large micro-service environment if every front-end service starts hitting an identity service the fan out is gonna kill you! You keep a basic set of information in token that is most often used, not every service will need user phone number, and all of his roles. 95% of your read traffic will need some basic ID, email, and basic roles that can be satisfied from JWT token. What OKTA is suggesting essentially is gonna push all the load onto a DB or some sort of datastore, so that it becomes SPOF, and now you have shifted problem to scaling that DB/Datastore. Answer IMO is it depends.


The article calls this out:

> If you’re building any type of service where you need three or more parties involved in a request, JWTs can also be useful. In this case the requesting party will have a token to prove their identity, and can forward it to the third (or 4th … nth) service without needing to incur a real-time validation each and every time.

But it doesn't seem like a good idea to expose the JWTs to the user directly: what if a user updates their phone number? This won't be reflected in already generated JWTs. It seems like a better idea to, at the frontend, generate any JWTs you need to pass to backends and use those to avoid fan-in to whatever identity service, rather than handing those JWTs directly to the client.


To be honest, if your JWT access token expiry is over ten minutes from creation and you have no way of invalidating tokens, you are doing it wrong.


Some concepts to think about:

local introspection: validating the validity and lifespan of a jwt by checking it cryptographically. does not need to make a network call, thus very fast and a great way to perform a low stakes operation.

remote introspection: validating the integrity and lifespan of a jwt by checking with the server that signed it and/or the server that issued the session. this should happen at least once per call flow, ideally as few times as possible but also every time sensitive information is accessed

if you have a heavy call graph, you can do local introspection on everything in the middle. and then, when you go to retrieve pii, do remote introspection.

if your token is appropriately short-lived (15 minutes), this means that an attacker can only yield information within that window with token scraping. that means that an attacker can only access data for as long as they have a foothold. i hope you have strong reporting and immutable architecture :)

> If you’re building a simple website like the ones described above, then your best bet is to stick with boring, simple, and secure server side sessions. Instead of storing a user ID inside of a JWT, then storing a JWT inside of a cookie: just store the user ID directly inside of the cookie and be done with it.

so basically, if your application is doing its own authn/authz (most monolithic applications), you don't need jwts.


no, you need to store the jwt in a cookie(or local store) because the jwt is signed. if you would have only cookie with user id then anyone would be able to pretend to be anyone by manually setting the cookie with some user's uid.


> if you would have only cookie with user id then anyone would be able to pretend to be anyone by manually setting the cookie with some user's uid.

That's what session id (as mentioned in the article) is for.


The alternative isn't storing user ID and sending it around but generating a unique session token that's stored for example in your DB. See how PHP does it (session_start etc).


Do not persist or store access tokens anywhere but in-memory. You can use a session cookie as a refresh token, and use it to get a fresh JWT whenever you need.


> Do not persist or store access tokens anywhere but in-memory.

Using short lived ATs is one defense against this happening, such as if an attacker compromises one of your services and starts scraping tokens off of it.

Ideally, the services should also have some sort of checks (such as service accounts), to ensure that only approved services can talk to each other.


using two tokens makes sense only if you have cqrs system. otherwise it's a complexity that brings no value.


This skips over one other big way that I’ve always seen JWT implementations fall over - sign out/session invalidation.

A JWT has all the information for verifying its own lifetime contained within it, which is cool in that you don’t need to hit the DB to verify it… until you want to invalidate it before the embedded expiration is hit.

Then you need to hit the database or some cache layer to verify that it isn’t invalidated, and now one of the biggest reasons to use it is gone.

They do mention that for CRUD operations you’ll need to hit the DB anyways, which is in the same vein as this issue tho.


Not really true if you use an access/refresh token system. The access token doesn't hit the DB for auth, the refresh token does. You have an access token with a lifetime of say somewhere between 1-10 minutes. If you want to invalidate a session, you just revoke the refresh token and any access tokens will be invalid soon after. In a system where a user is making 10+ requests in a minute, that can be well worth it to reduce stress on the DB without an appreciable loss in security.


Cookie sessions are usually stored in fast caches like redis so the performance hit is negligible. Most people will never reach the performance ceiling of a beefy single redis instance so why bother complicating the system?


Even when storing the cookies in a database like Postgres, they are fast enough because it's obviously indexed, and it's probably cached in many usage patterns. JWT is solving a non-problem for most people. Now, if you're talking machines talking to machines, all owned/operated by the same entity, go wild with JWT's if you want.


> Even when storing the cookies in a database like Postgres, they are fast enough because it's obviously indexed, and it's probably cached in many usage patterns.

If you scale up high enough, it does matter.

But beyond that, it is also about decoupling the server serving the content and the user database.

To give you an example, using a traditional session cookie model, whenever my server gets a request, it has to look up the associated session info, and then possibly join multiple tables to see if a specific user for that specific session is authorized to access that content.

There are going to be many joins involved. Even with good caching, it will still be a more complex operation.

Even if not computationally complex, it will be logically complex.

But when using a JWT, my server actually does not need ANY db access. It merely needs to verify the cryptographic validity of the included JWT header, which uses no IO. After that, it can safely trust any user metadata included in the JWT.

So, if a user has paid for access to all premium content after 2018, you would just include a field for that in your JWT payload. Done.

It depends of course on how your business data models work of course. But you can often put enough information in your JWT to cut out a lot of account processing logic.

> JWT is solving a non-problem for most people.

I think people are cargo culting JWTs.


> But when using a JWT, my server actually does not need ANY db access.

You don't need any db access with signed cookies either. Just stash your data in a signed cookie and you're done. Should you need more than 4k for session data, maybe it's time to rethink about what should be stored in the session and what should be stored in the db.


I know a good spec for signing json data to store it as a token here, too


When people talk about caching user information, access is one of the things cached. That complicated joining happens exactly as often with sessions as it does with JWT's.


No, with JWT parsing it doesn't even have to look anything up, cached or otherwise. The JWT itself contains the information directly.

I could have a server that doesn't have any access to the database at all, and it could still render content for the user.

And if the server is for something critical, like purchases, it can still use redis on each request to verify that a JWT is still valid.


I was responding to this:

> To give you an example, using a traditional session cookie model, whenever my server gets a request, it has to look up the associated session info, and then possibly join multiple tables

It does this on initial logon and then stores it in cache. You can think of a JWT as cache as well.

The point is, it only happens once for both.


Still not comparable. Session cookie requires a session datastore server side. It then requires to enable sticky session, to replicate the session between the server or to have a distributed datastore. if user information are cached it will require either a local cache or a distributed cache.

JWT token does not require that.

Both solutions requires invalidation.


I'm unsure why you're so hell-bent on arguing something so silly, but at the end of the day you made a mistaken point and I responded to it.

You want to try and move the goalpost that's on you, but your point about the joins is flawed. As is the rest of it, but I'm certainly not going to waste my time.


you can use signed cookies for sessions - which have none of the drawbacks you mentioned. i.e. session data and expiration form the payload, and you sign the payload - then you validate the expiration and signature on subsequent requests.


Assuming you mean express' cookie parser signed cookies? Or maybe rails?

Solves some of the session problems..

Benefits:

Cookies - when set to http only + secure only are safer?

Drawbacks:

They're not cross-compatible No expiration baked in No not-before baked in Limited to hmac validation (no public key crypto options that I know of)

Unless you mean using a JWT as a cookie value. I guess that could work?


no - i mean the generic concept of a signed cookie as I described, which would have none of the drawbacks you mentioned. They can even be encrypted, if desired.

There are many, many implementations in use "in the wild". They are just signed payloads, so they can contain "expiration", "not-before" or whatever else you think isnt "baked-in". You can use any method of signing them, but would probably make sense to use a strategy that is considered secure :) No doubt the libs you mention can be extended trivially with this "missing" functionality.

Signed/encrypted cookies predate JWT - they even predate JSON...

Im not presuming that they are better/worse than JWT, just responding to the incorrect statement about cookie based sessions requiring server-side storage


How does this solve it? You still need to wait 1 minute.


What security context are we talking about where that 1 minute matters?


A person has accessed your account and you’ve noticed it via sign-in-email. You change your password.

Attacker now still has 1-10 minutes to access your account.


I have this rant about people creating problems for themselves.

A perfect example is when SPA's were created, but it broke browser history. So we get a new standard with the ability to edit browser history.

I feel like refresh/access tokens fall into the same vein. If you need that don't use JWT's. The choice of JWT is the problem here, refresh/access tokens are a workaround.


> sign out/session invalidation

I wrote it up in another comment, but basically: if you're using JWTs, and you have lots of services calling each other in a request (as you might with microservices), it's entirely appropriate to have a session check ("remote introspection") when you access sensitive information, such as PII.

And, maybe, at the edge layer as well.


I feel like this point gets overblown. Subscribing to an "invalidations" message queue seems pretty straightforward. What am I missing?


Yeah that’s what we ended up doing but do hate that everyone who encounters this problem has to cook up their own solution for a scenario that’s very common.

It’s not terribly difficult to do because most JWTs should only last 10 to 15 minutes. We just broadcast the “jti” of the blocked JWT along with its expiration. The recipients just hold it in memory for that duration. That’s a relatively compact message so it’s quite simple to hold for 10 to 15 minutes.

One of you mentioned that it fails open. True but it’s mitigated by the fact that it’s only alive for 10 to 15 minutes (which you can still do a lot of damage with depending on scenario) and we repeat the message over a few times. In our case, it’s just being extra cautious since we’ve never seen our message bus fail. And if it does, functionality is impaired anyways for many of our services so it also inconveniences the attacker too. Maybe your scenario is different but in our case, it’s more than good enough. The attacker has to line up multiple holes to even get to this point. Or put differently, if an attacker is going to succeed in penetrating, it won’t be because we failed to receive a log out message from our message bus after someone has already logged out but before the JWT expired.


Can you explain what this looks like? So each application that authenticates its requests using the jwt subscribes to a message queue and replicates the data in some database local to it? Then it checks that database each time a new request comes in?


Pretty much, with the type of local db/cache changing based on the amount of concurrent invalidation you expect.

The theory is that your list of prematurely invalidated tokens (expired by user before the token's own expiry date) is much, much lower than your active tokens, so you only have to check a requests JWT against this tiny subset, rather than every active session to confirm it doesn't exist.

It has its own unique failure modes where an invalidation doesn't make it to all listeners, so either you can expend effort to make it more robust (and it's again, less data to sync than the sum of all active sessions) or just live with some parts of the system allowing the token for a few minutes..I

Typically, unless you are at a very large scale, or dealing with offline clients, I'd stick to traditional cookies + session.


That makes sense. Thank you.

Honest question - What would you do to justify doing this over using cookie based sessions? Like, are there back of the napkin calculations you could do to find when one approach becomes comparable in performance to another? That's one thing in system design that I struggle with.


Depends on how many invalidations you see. Some systems can get away with a message queue with an in memory copy of whatever's still in TTL with a bloom filter in front to make for a really cheap check. If you have more invalidations than a simple library implementation can handle, you do a similar thing with a DB like Redis sitting off to the side.


An invalidations queue will fail open if the queue does not deliver. If you check a session token against a database and the database is down, you fail closed.

There are systems at scales where you have to take that hit, but the overwhelming majority of people don't run them.


How many do you retain?


Not that many since they only last 10 to 15 minutes so you can just set the TTL to that. Also, you only need to retain the “jti” of the token and not the entire token. So you can hold quite a lot of these jti of tokens even in memory which is what we do. And then after 15 minutes they are ejected from memory.


Why not just have a lower TTL on your JWT?


Then what's the point? Just use sessions at that point.


PASETO tokens are superior to JWT in just about every important way, it's weird how they are so obscure and have not caught on. [1] https://paseto.io/


Paseto tokens don't really address any problem that is actually that important/relevant. JWT is good enough and when used properly have no security issues. Basically, sign them using a sane algorithm, distribute them over https and of course manage your private keys in a sane way.

Companies not capable of doing this properly have bigger issues that Paseto won't fix either. Such companies would do well to use products/frameworks based on standards implemented by people that do know what they are doing. And mostly those would rely on JWTs.


> When used properly

That's the whole problem. The majority of the time I've seen of JWTs, they weren't "used properly".


Can you describe typical problems you observed? I'm using JWT but I never gave much thought about it. It seems to work by default.


One of the most common high-impact issues is failing to expire sessions. In one case, the expiration date was set to be a whole year - once a user had a valid JWT, the system would accept it for a whole year, even if the user's account was deactivated on day 2.


> Our specialty is cryptographically secure PHP development.

Hm


If your snarky comment is aimed at PHP, I'm afraid I have bad news for you.

> PHP becomes the first programming language with modern cryptography in its standard library thanks to the bundled libsodium

https://www.jetbrains.com/lp/php-25/#e_2017_11_30_2

If the snarky comment is aimed at the developer posting the claim, yet again, wrong target.

Don't be like that please.


What about all the languages that can simply use libsodium directly? I don't think having it in the std lib is really such a distinguished feat.


> What about all the languages that can simply use libsodium directly

Nothing. It's great they do that.

I made a comment that refuted a snarky remark that had no basis, I did not compare languages nor post claims about language's awesomeness or anything else, just that it uses libsodium and that silly remarks centered around trashing PHP are based on lack of knowledge.


I agree the snark on PHP doesn't have much sense. I just don't think your reply is much better. This changes nothing - if anything, it shows that PHP needs to have libsodium integrated before it's available, which seems to be worse than other languages that can simply use it directly.


So, there's a library called libsodium that deals with cryptography and your comment, based on literal nothing, is that PHP is somehow "worse" than other languages, yet PHP and other languages use libsodium for cryptographical purposes. What does "worse" mean? Somehow, the bytes get corrupted mid-transfer? We're arguing languages here for no reason at all, yet you are not even aware of how PHP uses it (spoiler: it uses is like the other undefined languages do, since there's only 1 way to do it).

If you got any arguments to support your "which seems to be worse", I'm readily waiting to read it.

Out of curiosity - why do you try to instantly compare A to B without knowing how A or B work internally? What's the gain in this discussion other than you making a claim you can't support and me being a jackass that wastes his time trying to talk to you?


It's worse in that you need to wait until PHP adds it into the library, instead of just downloading it and using it.


libsodium has been available in java via JNA since 2017 but go off


my comment was snark about the author, not the language, this site is ridiculous, and so are the claims about JWT


Bundling libsodium in the standard library doesn't really seem to be a mark for or against PHP.


His first recommendation is "not to write secure software in PHP," so I'm "Hm"-ing at the fact that he sells his business this way? but ok


bundling libsodium is just a compliment about libsodium


leave it to PHP guys to assume they are under attack and downvote instead of merely checking their inputs


Also from the same author (actual titles):

- Multi-Factor Authentication Sucks

- Nobody Cares About OAuth or OpenID Connect

- What Happens If Your JWT Is Stolen?

- Why JWTs Suck as Session Tokens

I would rather write an article titled: Why all those blog posts suck


This is a great summary of why this guy's blog is a waste of time honestly. Thanks for doing the work!


The titles are misleading (purposely, because it attracts interest), however - you should read what's written before judging.

From what you've written, I'd safely bet your attention span is really low and that you haven't read a single one of those before writing the comment here.


> The titles are misleading (purposely, because it attracts interest)

I am neutral to this conversation but thought you might not want to justify this point as it is against the spirit of HN. Specifically in the guidelines:

> Otherwise please use the original title, unless it is misleading or linkbait; don't editorialize.

Which would give the submitter permission to change the title to something more substantive.


When you read the posts and when you don't rely on titles, you can see the point that the author argues.

2FA does suck, it's not a pleasant experience but it is necessary. If something sucks, it does not mean that you don't have to or should not have to use it. There are things that hinder user experience but are immense for security, like 2FA is.

Making judgment based on title is silly, and if the spirit of HN is to judge without facts - then I apologize. Carry on as you were.

The strawman argument against the author, based on the title that provokes thought, is not a sign of intellectual discussion.


I am in agreement with what you are saying.

> and if the spirit of HN is to judge without facts - then I apologize.

Although snarky, I am sure you would appreciate if HN doesn't devolve into sensationalist titles such as: "PHP sucks" ad nauseam since there are plenty of social media networks that get flooded with titles like that already.

Just as we (the audience) in good faith read and judge based off of the merits of the article's content - one could also argue that an author should treat the audience with similar courtesy of not needing to:

> misleading (purposely, because it attracts interest)

Not saying this applies to this article since in my subjective view I didn't find the title misleading or egregious.

You might also be interested in seeing the recent edit history of titles on HN

https://hackernewstitles.netlify.app/


PHP does suck though, and OAuth doesn't.


The titles may not be useful, but the posts are generally correct.


While I don't necessarily disagree, I think using the globally accepted way of somehow utilizing JWTs outweigh the marginal and exaggerated (24MB for 100K requests is literally nothing in 2022) benefits.

If I use JWT I get widely battle tested library support and near-universal acceptance among developers, which is more than enough for most of the folks to go with JWT IMHO.


JWTs are great for fast, efficient, distributed authentication. You shouldn't store too much stuff in the JWT, just the username and access level is generally enough. The trick is to set it to have a short expiry and keep renewing while the user is online/active.


I’ve been building software for 35 years and web apps since “the beginning.”

You couldn’t drag me back to server side state management for anything.

OAuth2 and JWT tokens is one of best inventions and has proven itself quite thoroughly.

This article was goofy in 2017 and it’s even goofier now.


Jwts are great for micro/multiple services -- no need to hit the auth db on every request.

Determine ttl by considering the context and then balancing performance/security concerns.

If you need to invalidate a session before the ttl expires, throw the identifier in a memory cache. But I think this is overkill for most applications. Rather, protect important state changes by asking user to enter password or to provide other form of auth.

If the frontend wants access to the jwt data, a pattern I like is splitting the jwt by keeping the header and payload in js readable cookie or local storage, then keep the signature in an http only cookie.


What an awful article. Why is it talking down to their readers like that?


I found Security Cryptography Whatever's treatment of the various session token options super informative [0]. I had some vague unformed skeptical opinions about JWTs, and now I have some reasonably informed rational opinions about them, so can recommend.

[0]: https://securitycryptographywhatever.buzzsprout.com/1822302/...


>> “ A mission critical user check needs to run (eg: does this user have enough money in their account to complete the transaction?)”

That info wouldn’t be in the session db row either.

>> “ A database write needs to occur to persist information (if this information is related to the user, it’s likely that the full user object must also be retrieved from the database”

This type of info doesn’t get updated frequently. Email, phone, name, etc, are pretty static. If they are just talking about a join using a userId, well that’s not gonna be any different whether you know the ID from a JWT or normal cookie.

>> “ The full user object must be pulled out of the cache / database so that the website can properly generate its dynamic page content”

But that’s an upside of keeping more than a cookie with an ID. You can just stash the stuff that doesn’t change much client side (keeping in mind the XSS risks). We certainly don’t pull the user’s email and name every page load even though it’s displayed on every page.

>> “ Almost every web framework loads the user on every incoming request. This includes frameworks like Django, Rails, Express.js”

That’s a framework issue and should be customizable. Not really a JWT vs cookie w/ ID topic

There’s plenty of reasons to choose an ID cookie vs a JWT but the many that the author gives are not among them.


JWTs survive in popularity because, by and large, their size is negligible over the wire, internet speeds get faster (not slower), and key agreement across systems is still a surprisingly complex problem.

(1) Size Factoring Gzip and HTTP/2 header compression into this, size is no longer an issue, but it's still a reasonably good idea to keep claim sizes down. If you don't like using cookies, use a sessionStorage value and affix via XHR! Easy

(2) You're Going to Hit The Database Anyway No you aren't, necessarily? Wth. Also, JWKS paired with JWT gives fantastic rotation security with very minimal configuration across systems. So, maybe you don't even have the database accessible to you. You can still verify your signatures.

(3) Redundant Signing Sounds like this is advice for some framework that signs the session cookie? This is silly.

"You should use Session IDs instead"

Tell me you haven't built a globally distributed system without telling me you haven't built a globally distributed system.


I haven't built a globally distributed system, and most people here never will. I'm not against JWTs, despite the constant influx of hackernews articles, but "globally distributed system" is not a valid argument against using session IDs when most of us are working with a single fast db.


many apps these days are globally distributed virtually by their nature.


JWT as a term used to describe signed material should considered harmful (source: me)

It's basically just signed authentication material

Use it when you need to keep your authentication layer horizontally scalable & stateless

Use it when you're transferring trust to an untrusted system, acting on behalf of a user (SAML, OAuth, OIDC)

that's it

It's just signed material

data size limitation (from the blog) is a big meme (source: me). Just pack it into a protobuf lmao

eventual consistency of a stateful session will hurt you btw

yes yes yes I know. The grey bears gave us protocols, rfcs, etc. MUH PROTOCOLS. just give me a private key and a bud light dude

the protocols are important for oidc/oath/saml

JWT ISN"T A PROTOCOL

i'm tired, I should go sleep


Related recent discussions (different articles on a similar topic):

https://news.ycombinator.com/item?id=33019960

https://news.ycombinator.com/item?id=33018135


>In this case the requesting party will have a token to prove their identity, and can forward it to the third (or 4th … nth) service without needing to incur a real-time validation each and every time.

Someone missed the entire point of audience claims in OIDC - on okta.com no less.


Randall has also given a number of talks on the topic, so if you prefer watching to reading, have a look at https://www.youtube.com/watch?v=JdGOb7AxUo0


At this point, JWTs are a cargo cult. People do them because everyone else does them, even when it makes 0 reasons in the app itself and they use them as worse session tokens.

Nobody was ever fired for using JWT for authentication.


Has anyone tried https://www.biscuitsec.org/ ?

I haven't seen it much discussed, and seems to solve a lot of issues from JWT


This was definitely written pre Auth0 acquisition, just saying




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: