JWT is a scam and your app doesn't need it(dusanmalusev.dev) |
JWT is a scam and your app doesn't need it(dusanmalusev.dev) |
> just put the JWT in an httpOnly cookie
You can have two cookies, one that is signed and httpOnly, and another that is unsigned and readable by JavaScript. Both contain the same information. So JavaScript can read the information in the second cookie, but since it is unsigned, exfiltrating the cookie doesn't compromise security.
That's a narrow scenario isn't it, if you have to "immediately" realize?
Why would you want to deal with a bespoke session and authentication table when there's a standard solution out there already? What's the point in figuring out your custom little cookie signature system when every frontend and backend under the sun have a solution ready to go? And somehow, somewhere, Redis is now part of the design to prevent having to do a database round trip for every cookie?
Sounds to me like this is a whole load of work for little benefit. Sure, session cookies just work most of the time, but they have their own disadvantages.
The only tangible benefit to session cookies is that you can log out all existing sessions at the cost of doing a lookup for every request. Most websites don't even offer that functionality, though.
Typical production architecture would look like - frontend only ever sees an opaque session cookie - bff stores the access token against session and attaches it when calling backend services
Yes, storing JWTs directly in the frontend client is a bad idea but surely there is a better way of communicating that than "JWT is a scam and your app doesn't need it".
> RS256 verification is in the same order of magnitude as a Redis lookup
But the point is that the verification is CPU bound and local to the service - which means that it is horizontally scalable.
https://www.pangram.com/history/c68104cd-6072-4f7a-850b-e534...
Authentication is the process of verifying who a user is, while authorization is the process of verifying what they have access to.
Note the opening claim:
> JWT promises stateless authentication and delivers neither
JWT claims typically are used to pass identity of authenticated users, not to authenticate users, that is important, and I cannot validate a opaque bearer token at all, while we do need something better, opaque bearer tokens are not an improvement for authorization.
> You can't. That's the answer. The token is valid until it expires, full stop. The only way to invalidate it is to store the jti server-side in a revocation list and check that list on every single request. Which is a database lookup. Which is the thing JWT was supposed to let you skip. Congratulations, you've reinvented sessions, badly.
Bloom filters, which are constant factor time add/remove, constant-space set data structure are trivial to implement, if you have something like the expiry time of a jwt to restrict their growth. Even if you had a scalable bloom filter for a narrow temporal high volume need, the sizes are tiny and the performance avoids needing to pound your redis revocation list.
While JWT doesn't have the information hiding capabilities of opaque tokens that must be validated through introspection, with every request needing a call to the authorization server or local cache, Nothing with JWT stops you from doing exactly the same, either in the positive case with a SPoF authorization call, or in the negative case with a bloom-filter in front of a revocation list on redis.
Basically all the options have limits, costs and tradeoffs.
Choosing between JWTs and opaque bearer tokens is a least worst option choice, About which one fits your application’s architecture, security model, and operational needs.
If you need information hiding JWT isn't probably the best choice, if you want flexibility, observability, and performance opaque bearer tokens are probably not the best choice.
Remember there is that nuance and temper your expectations from the clickbait, they don't know your system, your needs, or your risk tolerance.
This is a shift in complexity, not a change in complexity.
And don't pretend that the 2 are not related because typically an OIDC provider is the thing issuing those JWTs.
So, can you simplify, sure. And now every part of your application needs access to that same table of sessions to get revocation.
It works fine for simple applications not for large solutions with many different systems that cross org boundaries. Because in a lot of orgs the boundaries of the services are more organizational than technical. If you want to be the one that makes them all depend on your SPOF, go ahead, I want to see you sell that idea to your CTO
If your frontend application connects to multiple protected APIs, you just can't use a session. That's it. Mobile apps and some specific web application need this a lot.
The only true claim I see in this post is > almost every developer shipping it has no idea why.
That's the true problem. JWT is being used as a SSO strategy in the wrong way most of the times.
The entire argument the article does about invalidation confirms this: the bit about 5 year tokens. You do automatic token refreshes, which requires 5 extra lines of code on the client, and one network request every 30 minutes or so. Log out? Remove the token from the client, and let it expire in the next <30 minutes. Safe enough for most use cases.
It's fine to default to jwt, but yeah obviously you can screw it up if you implement it bad enough, and there are certain use cases where token expiry might not be safe enough (banking for example. But honestly with a short enough TTL it might be fine even there. You can totally do 5 minute tokens with automatic refresh gated behind user activity, if you need tighter security)
In the per-request cost table the author complains that signature validation is expensive, yet it's ~half the cost of a redis lookup according to the same table!
To me the entire article sounds like arguing that running shoes are bad because the author got a belly ache after eating one.
So it's fine for some services to serve requests after the access has been revoked for a few extra minutes or seconds.
For some it isn't. I have no say on the matter, I feel like security lax-ness makes sense for games, since jacking sessions with jwt is significantly less consequential and you have other sources to validate session health, such as route and ip address.
You can always revoke jwt if the ip address for which it was issue has changed or the route is different and so on and can be even more secure against forgery attacks.
Still not perfect and I find OP's take the it's good enough for banking to be sus.
But it's definitely not a scam, it's simple easy and trivial to use for a lot of low risk services.
You should build a map of low risk and high risk access and not use jwt for anything high risk, because jwts definitely provider better user experience for relatively less effort and resources and technical complexity needed.
The article doesn't need clickbait titles either, which is also not a savory practice. Other than that it is good to educate people to make informed decisions on JWT.
If you drink all of the koolaid, you can wind up with a system where two different parties securely authenticate sessions without any kind of secrets ever needing to be provided directly. Both parties generate and retain private key material in an HSM and perform signing ops there. The only thing that has to be trusted is the subject of the certificates. Keys can be rotated arbitrarily and the counterparty should not have any issues with this, assuming the standards are followed.
Short lifetime is the best solution to concerns around revocation. The vendor I last integrated with has their opaque tokens expire after 15 minutes. Immediate revocation sounds preferable but in practice there are other controls that will typically compensate for any gaps here (i.e., audit logs & the police). If you are firing someone and you think they might do a really bad thing that the business could never recover from, you should probably disable their accounts about an hour before the call with HR.
If only that was true. JWT came with a lot of questionable cryptographic choices. It went hard on fine-grained cryptographic agility like it was 1995 all over again. It started with the wrong suite of outdated ciphers like RSA and no good security requirements and implementation guides, and even the notorious "none" algorithm. The end result is a string of CVEs that affected a wide swath of JWT libraries.
JWT has many other ill-conceived features such as embedded JWK, token-declared URLs and the complex mess that is JWE (which you'll need if you want encryption). This complexity, useless features (the "none" algorithm), together with picking algorithms that are hard to implement correctly[1] and not setting any kind of security requirements made vulnerable libraries spread like wildfire, and introduced at least 5 different classes of vulnerabilities[2].
The craziest thing, is that easiest vulnerability to abuse (the "none" algorithm), is a required by the JWT RFC, you read it too literally:
Of the signature and MAC algorithms specified in JSON Web Algorithms
JWA], only HMAC SHA-256 ("HS256") and "none" MUST be implemented by
conforming JWT implementations.
Now, the RFC writers obviously meant that conforming JWT libraries, should support "none" if explicitly requested, but did not mean that the libraries have to accept the "none" aglorithm by default. In fact, it's clearly stated: An Unsecured JWS uses the "alg" value "none" and is formatted
identically to other JWSs [...]
Implementations that support Unsecured JWSs MUST NOT accept such
objects as valid unless the application specifies that it is
acceptable for a specific object to not be integrity protected.
Implementations MUST NOT accept Unsecured JWSs by default. In order
to mitigate downgrade attacks, applications MUST NOT signal
acceptance of Unsecured JWSs at a global level, and SHOULD signal
acceptance on a per-object basis
Or in another section: Unsecured JWSs (JWSs that use the "alg" value "none") provide no
integrity protection. Thus, they must only be used in contexts in
which the payload is secured by means other than a digital signature
or MAC value, or they need not be secured.
Unfortunately, the JWA spec is huge and complex because it needs to definy many useful, secure and totally not bonkers algorithms like PBES2-HS256+A128KW and RSAES-PKCS1-v1_5, and the very important(?) reasons for storing the coefficients for RSA. So I guess the end result is that almost nobody ever read the JWA RFC, and somehow half of the first crop of JWT libraries let anyone strip the signature, change "alg" to "none" in the header and freely manipulate the token. Good times.So yes, stateless tokens are a bad tradeoff for many (if not most) web applications. But even if they weren't, there many formats that would do much better than JWT[3]. If you end up going with JWT, it's really better to thoroughly review your library, and stick to safer algorithms that doesn't result in a 5kb token (I'm looking at you RSA). Ed25519 is now supported by a decent amount of libraries.
[1] https://neilmadden.blog/2022/04/19/psychic-signatures-in-jav...
[2] https://pentesterlab.com/blog/jwt-vulnerabilities-attacks-gu...
I mean, honestly, there are really only two options here:
1. You don't know how to "tune" the LLM output, or
2. The LLM output can't be tuned.
Either one means that you should probably write your own thoughts and not have a probabilistic generator create this word-salad that I found extremely hard to follow!
> You can't [invalidate a jwt]. That's the answer. The token is valid until it expires, full stop. The only way to invalidate it is to store the jti server-side in a revocation list and check that list on every single request.
...So the article says you can't invalidate a jwt, and not 2 sentences later, tells you how you can. Of course you can.
> [misc criticisms of refresh tokens]
Mostly agree with this part.
> RS256 verification is in the same order of magnitude as a Redis lookup. And you still have to parse JSON, validate exp, validate iss, validate aud, walk the JWKS cache for the right key, and allocate a few objects the GC has to clean up later. Multiply by your request rate.
I think this misses the mark a bit. Signing/validating a jwt can be done entirely on your application server, which should easily scale horizontally. Any kind of db lookup requires a roundtrip/bottleneck which introduces complexity. So, even if it's the same # of ms, there's still an inherent advantage for validating on your application server IMO.
> Almost no one does [front end verification]
Sure, agreed, but so what? It's a feature that no one uses. That doesn't invalidate other features of jwts.
> The advice goes: don't put the JWT in localStorage because XSS will steal it — put it in an httpOnly, Secure, SameSite cookie so JavaScript can't touch it. Read that sentence one more time. You have just described a session cookie. The browser attaches it automatically, the server reads it on every request, the client can't see what's inside.
Yes, I think from a client perspective, it's fine to think of it like just a session cookie. As many other commenters have pointed out though, it's like a session cookie _with super powers_ because once the request is validated by some kind of front end app server, you can pass that same token around to other backend server and trust the claims inside it and save each of those services from having to do their own lookup. This I Think is the real value of JWTs over plain ol' session cookies.
Eveything else can use plain tokens stored in the DB
You just haven't understood what JWTs are good for. See my other comment in this thread.
No, the rest of the post was written by an LLM.
I guess if you need your tokens revoked this millisecond it’ll require an extra synchronous call on every request but that seems like a unlikely requirement. And not that any of this matter for your 10 user app anyway I guess.
The uses described in the article assume that authorization of access to resources is handled in some way external to the JWT. This literally takes a system designed to support authorization, ignores that, and uses some other back-end authorization mechanism.
One of the most important features of JWT is its support for capability security[1], via signed claims. If you're just using JWT to authenticate, you're kind of missing the point.
> The payload usually holds a user id, an iat, an exp, a jti, maybe some scopes.
This demonstrates the point nicely. At best, scopes provide some sort of broad authorization such as "read" or "write". But what if you want to prevent IDOR attacks[2] at a low level in your system, to eliminate any possibility of developers accidentally allowing them? Tokens with nothing more than scopes don't help with that at all.
All you need to do to solve that is to add the entity types and IDs that the bearer is authorized to access. So if they're going to a page that displays an invoice, then in the JWT claims you include "invoiceId" (often along with some kind of type field, like "type": "invoice".) The target page checks that the JWT matches the requested resource, and that can indeed be done without any further back-end verification. You would also typically include a tenant ID and other such authorizing identifiers.
Doing this alone will give a system stronger security than 99.9% of systems out there.
Regarding revocation, the point about the above approach is that the tokens are essentially single-use: they authorize access to a particular resource for a short period of time. Basically, whatever the normal "log user out after inactivity" timeout is, the token expiry should be no longer than that.
If you create tokens valid for days or weeks, that's a mistake. You can prevent this simply by giving devs a library that creates the tokens according to org policy.
So yeah, JWTs purely for authentication and doing authorization some other way is a dumb idea, but that doesn't make JWTs a scam, that makes the user ignorant of their real purpose.
[1] https://en.wikipedia.org/wiki/Capability-based_security
[2] https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Dire...
I don't get it. Why were you lying to people??? Why were you pretending? Thats not healthy and pretty anti-social.
You cannot refactor an battlecarrier designed in 1979 so easily ;)
One thing that I did AI generate is the website itself. I really didn't have the time to do it on my own and I vibe coded it
> A single opaque token, looked up in Redis with Postgres as the backing store, gives you the same security in one line of middleware. No refresh. No second token. No retry loop. Nothing.
This is one classic example of LLM writing, humans don't write like this. The blog post has tens of other tells.
Of course the only completely hack-proof device is a brick.
You dont need JWT, just use something else and you will be fine, this is the gist of it.
> Sure, agreed, but so what? It's a feature that no one uses. That doesn't invalidate other features of jwts.
True is unused feature, but then stateless part falls apart, you don't save anything but introduce complexity and maintainance problems, someone will have to go over that code, understand it and not crew things up.
> I think this misses the mark a bit. Signing/validating a jwt can be done entirely on your application server, which should easily scale horizontally. Any kind of db lookup requires a roundtrip/bottleneck which introduces complexity. So, even if it's the same # of ms, there's still an inherent advantage for validating on your application server IMO.
My position here is, if I have to hit the database, well why then I need the signing part, or encryption part, whats the benefit of it. What I understand from JWT, you dont have to store anything. That was the promise of it, and yet for secure systems you have to store something, at least to be GDPR compliant. AFAIK you need to provide the feature LOGOUT FROM EVERYTHING by GDPR, dont quote me on that, it's what I've seen, not a lawyer, simple developer.
But hey, since jwt is so insecure, why don't you go ahead and hack my Minecraft account? I implemented the JWT -based Auth they use, and my username is iworkatmojang.
Get back to me when you've changed my password
If you want, however, we can simulate the scenario. Find your JWT token, change your password (commonly invalidates tokens aswell), and then post it here post-invalidation. If it works still, then thats the issue. Though, changing your password commonly requires your current password and not just a cookie, so I wouldn't be able to do that. But I could probably change your username as proof. If it doesn't work, then it was checked against some revocation database that the article talks about, where at that point, where you have you check a database anyway, you might as well just store the session on the server, since the JWT is no longer stateless and provides no advantage over typical sessions.
It can be done right, but it's harder than doing something else. Take it like this: IF YOU KNOW WHAT YOU ARE DOING, OK, FINE, GOOD LUCK IF YOU DONT, STICK TO SOMETHING SIMPLER.
Simple solutions are better solutions, and developer time is important. Do you want to maintain simple authentication layer or you want this complex machinery, upgrade library, check for CVE, validate the library implementation, read the RFC. At that point I would be like... NO, I wanna go and do other things I'm interested in.