Plenty of good uses for JWTs for service-to-service communication.
edit: I read some of the linked stuff, e.g. https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba... . Please, if JWTs are such a horrifically insecure standard, go ahead and publish your means for hacking AWS STS's AssumeRoleWithWebIdentity , or don't publish and just exploit it by launching cryptominers in every Fortune 500 production AWS account. Let me know when you inevitably succeed, because JWTs are so insecure, right? /sarcasm
JWTs are too long lived... Nothing is stopping you from limiting the JWT lifetime and having a refresh model against an authentication authority... I mean, even if you use cookie based sessions, you're storing somewhere... you can have a jwt valid for 5-15min. 15minutes is roughly the cache timing for many authorization systems including Entra... and even a 5min token with a refresh system can be used fine from a browser.
Lastly, I prefer to have identity/auth separated from the application/api services... it externalizes context and JWT per request is easier to deal with than some shared cache/state system that may intermittently fail as opposed to a signed token that you can verify the signature against known authorities.
>The JWT specification itself is not trusted by security experts.
This feels like it needs more evidence than just one blog post. And that blog post seems to just largely blame bad implementations? Something that will plague any standard.
Overall, I don't know what I expected clicking a random gist link.
When using sessions, your list of valid sessions is probably orders of magnitudes higher that the revocation list - thus the data lookup costs and the storage cost of that statefulness is higher.
Plus, the article mentions JWTs are stateless but that is usually not true. You mostly not only validate the JWT, but also obtain a matching identity object (i.e. user details) for each request to see if the user is still enabled/authorized to do whatever he does. You can leverage stuff such as per-user revocation lists, or a minimum_issued_at that will validate any JWT iat field. This allows the "Logout from all devices" pattern, where that action will simply set a user's minimum_issued_at field to $NOW. All previous tokens will thus be revoked, without individuall revocation list checks.
For web/mobile auth where same server issues the JWT and same server verifies it, it makes no sense. JWTs cannot be invalidated. If a user loses some permission or account gets disabled, JWT will still be valid until its expiration time. Servers must either make DB calls to verify the user is still active or be fine with deactivated users having access for a while after account is disabled. This completely defeats the purpose and bearer tokens work perfectly for this use case.
JWTs should _almost_ never be used in client side auth. Client should send regular cookies and bearer tokens. The auth server can internally generate a short lived JWT and inject it into requests before they get routed to various services internally so those services don't need to query DB every time to verify the user.
Some nice topics to talk about instead:
- When to use an encrypted value (and symmetric or asymmetric), vs. a random (but secret) value, vs. a signed value (readable but not tamperable)
- Where to put these values (memory, localStorage, cookies)
- How to make sure these values don't last forever, and whether you need to be able to revoke them (make them invalid before their natural expiration timestamp)
Anyhow, there are way smarter people than myself who have covered this topic extensively over the years, but I still think that, even in 2026, JWTs are the wrong tools for web auth. They're fine to use for service-to-service stuff, but if you have the option, just use PASETO -- it solves a lot of the issues!
This article just sounds like heebie-jeebies, at best it's someone saying something about JWT doesn't smell right (because it can be used incorrectly,) at worst it's a pissing match / religious war.
This article would be more credible if it had a tangible explanation of a real exploit.
(BTW: I don't understand how cookies are inherently "better" or "worse." They can be sniffed and replayed too.)
I don't see another setup that comes close to the ease of setting this up - add an endpoint that provides jwt tokens to valid sessions, done. With user-individual permissions.
Just to clarify, httpOnly/sameSite isn't useless under XSS the way localStorage is. XSS can't read a httpOnly cookie, so it can't exfiltrate the credential, it can only perform the attack during the session from the victim's browser. A JWT in localStorage can be reused offline for its entire lifetime. Also worth separating: localStorage is the exposure, not JWT. Just please for the love of all that's good and pretty, don't store a JWT in a httpOnly cookie.
This has several advantages, the main one being that sub-services do not have to interact with the authentication database or have access to the capability to mint tokens (this assumes you use RS256 not HMAC). So if a sub-service gets compromised it's not as devastating as a service which has access to the authentication database.
If you have sensitive data inside the token you should use JWEs, although they're not as good because you have to ask an internal service (which has the private key) to decode the token each time you want to use it.
My typical layout is {"id": (uuid), "scopes": ["scope:read/write"]}.
Also they're really neat for SPA's as you can have your static site server validate that the JWE with the public key before serving any resources. The way I use this is that I have my static site compiled to /(scope)/path and the static service will not serve pages that you cannot access anyway. This is very useful in cases where you have administrative panels where you don't want to expose to users what capabilities your backend has or/and expose the internal service paths that can be attacked.
My lifetime for JWT's is around 5 minutes for "backend access", things like /me are cached in localStorage unless explicitely instructed in /refresh to drop localStorage cache. My request handler in my SPA applications detects "refresh required" and refreshes the token.
I think most of the blame here belongs to node/next and python libraries. I write my backends in strongly typed languages and my frontend is always made out of precompiled static pages. My current setup for the frontend is using VITE with prerendered pages for landing and normal SPA for applications.
With all of that said I strongly disagree with this entire gist. JWT is as secure as you want it to be.
https://datatracker.ietf.org/doc/html/rfc9449#name-dpop-proo...
The JWT specification itself is not trusted by security experts. This should preclude all usage of them for anything related to security and authentication.
Very bold claim that seems to ignore all the iteration and hard-won lessons on this from the ecoystem...- Use ES256
- Always set the JTI to be completely random
- Set iat (issued at time) and exp (expiration time)
- Set iss (issuer) and aud (audience) to match your application
- Set sub (subject) to match whatever unique identifier you use for users
Store the hash of the JWT in your database with a lazy cleanup hook on the expiration time.
Now, you can use this JWT for a cheap WAF at the edge!
Token expired already? No need to query the database, reject.
Audience doesn't match the requested URL? No need to query the database, reject.
Signature doesn't match your public key? No need to query the database, reject.
Everything passes? Query the database for the token hash.
Token hash not in the database? Add the token hash to the WAF's cache (with lazy cleanup hook on the expiration time).
Everything passes but token hash in the WAF cache of rejects? No need to query the database, reject.
etc
See the benefit? It's defense in depth. If you screw this up, all you lose is the WAF layer.
I just noticed that after JWT was created, people would just slap on JWT like an end-all because JWT sounded secure and they thought it was all that they needed to do.
That’s my only “problem” with JWT but to be honest, people will build insecure systems anyway.
A user wants to access a read-only resource with an invalid JWT? Envoy bounces it without passing the request through to the backend. Valid JWT? Let the request through without having to look up any session information. No DB, no cache, no session server hit. Fast.
A user wants to change a password, email address, or add an authenticator? First, require a password, second, require a second factor. If all of that checks out, look for the JWT access token in a revocation list that is only accessed during sensitive, infrequent, requests like these. If the token has been revoked, 403.
Tokens are dropped from the revocation list once the original access token's TTL has passed. Which should be low. I use 5 minutes. Most sessions on my site last 4-10 minutes.
Worst case scenario, a malicious user is able to access certain read-only resources for a few minutes.
https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
It boils down to "there were bugs in some of the libraries" and then goes on to recommend you...pull in libsodium and do it yourself??? This is ludicrous advice that I simply can't take seriously. All software has bugs. The whole Internet lost its shit with Heartbleed, but we still use TLS and OpenSSL.
> The JWT specification is specifically designed only for very short-live tokens (~5 minute or less).
I've never heard this before and can't find any evidence to back this claim up. RFC 7519 doesn't make any such claim.
> If you do need a short-lived, signed token for something, there is a better spec called PASETO which is designed to be secure
Suggesting to non security people (like myself) something for auth that isn't a mainstream idea seems like a bad idea? Not to mention that it doesn't refer any reasons why it's better
This is quite a loaded statement. Why is it better to store all the data? What if you have a CDN layer that only needs to do routing based on authentication or scope, or other token encoded data?
This point is not made very clearly and is buried by overemphasising JWTs instead of just quickly pointing them out as an example of a stateless session. But yeah, it is a good point.
> And there are more security problems. Unlike sessions - which can be invalidated by the server whenever it feels like it - individual stateless JWT tokens cannot be invalidated. By design, they will be valid until they expire, no matter what happens. This means that you cannot, for example, invalidate the session of an attacker after detecting a compromise. You also cannot invalidate old sessions when a user changes their password.
> You are essentially powerless, and cannot 'kill' a session without building complex (and stateful!) infrastructure to explicitly detect and reject them, defeating the entire point of using stateless JWT tokens to begin with.
I'm not sure that this is entirely true. Typically, the total number of non-expired issued tokens is much higher than the number of invalidated unexpired tokens. Therefore, if you store only invalidated tokens and delete them when they get expired, you can significantly reduce the amount of required storage and the cost of lookup.
Although, in any real application the performance gains will be minuscule (compared to the cost of, you know, everything else. Auth is just a small part) and probably not worth the extra complexity.
[0] "Stop using JWT for sessions" - http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-fo...
I don't think the cryto.net post really explains why this is true (at least in a way that would be made different by "massive resources").
It's far better than a session ID in the sense that you can store an accountId inside a JWT and be confident about the identity of this user... The fact that you can get such confidence without any external lookups; just by looking at the token, is incredibly useful on its own. You can already seperate out unauthenticated guest users from authenticated users and you can tie their identities to real accounts without even checking the DB or session store. Banning is a separate concern. Being able to quickly, efficiently and reliably identify a user from the server-side is a necessity.
The only real downside of JWTs which people point to is that they cannot be easily be revoked... But if you really need the ability to revoke quickly, you can easily have 10 minute expiries on your tokens and request refresh every 3 minutes or so... So if you want to ban someone, there would be a 10 minute delay which is acceptable for the vast majority of scenarios... You should handle rate limiting as a separate concern anyway if spam is the issue.
For certain systems, a user may be making hundreds or thousands of requests in 10 minutes so you're saving a HUGE number of session ID lookups and it means that you don't need to run and maintain a separate Redis service or whatever else. Not to mention the additional latency which is added when you need to check Reddit before each operation.
These blanket statements against JWT are not new. People have been misusing them and blaming the tech because they don't want to acknowledge that they made implementation mistakes.
Any technology can be misused. It's foolish to misuse a perfectly good piece of tech and then use that as the basis to promote some alternative which has been through less battle-testing and which probably has even more gotchas which are yet to be discovered.
As a senior engineer with 15+ years of experience, I've seen this cycle over and over again. The new tech is always presented as fool proof and it never never never is.
Using them as the primary source of truth is an anti-pattern like the blog post is actually saying.
JWTs are just tokens like session data but in JSON format. What format you choose to go with doesn't matter.
You can keep storing JWTs in local storage and still be secure. Discord removes it on page load and restores it when the tab is closed.
Also if your website is susceptible to XSS, skill issue, exactly like in the case of SQL injections. That wouldn't have happened had people used the right tools and not played with fire.
Citation needed. Where does it say this?
The JWT specification is specifically designed only for very short-live tokens (~5 minute or less). Sessions need to have longer lifespans than that.
Huh? The expiry is as long or short as you want.
The JWT specification itself is not trusted by security experts.
...and we're supposed to trust some random gist? Pure appeal to authority.
> A lot of people mistakenly try to compare "cookies vs. JWT". This comparison makes no sense at all, and it's comparing apples to oranges - cookies are a storage mechanism, whereas JWT tokens are cryptographically signed tokens.
And yet the author seems not to have noticed, or something? Odd.
and the JWT can transport a more encrypted user session as a value
just be intentional
Please, keep using JWTs, they do their job well: giving you an access or ID token that you can pass between applications and trust based on cryptographic signatures from an identity provider.
Yeah, right.
JWTs are fine, as long as you use sane algorithms. The missing revocation really can be done by replicating revocation policies. E.g. a direct list of revoked tokens or a blanket "don't trust tokens before this timestamp".
If you are operating at a scale where you can simply store session data in the database and look it up every time, that's a fine way to operate. At some scale this approach becomes a problem, and it's faster/cheaper/simpler to store some limited data on the client (signed).
Yes there are complexities to both approaches. That's fine.
[0] https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
This year, I ended up publishing an open protocol that can be used for everything from secure authentication to API requests to micropayments, and it works with the existing Web stack and P256 curve with JSON serialization curves as well as EVM blockchains via K256 curve and EIP712 serialization.
I encourage everyone to take a look at it and consider using it, so we can stop reinventing the wheel. Everything has already been deployed, it’s an extremely simple protocol, much simpler than JWT and requires no global registry.
https://github.com/OpenClaiming
It is also used in stuff like https://safebots.github.io/Safecloud/
> Sessions need to have longer lifespans than that.
Why is this JWT issue. JWTs generally have refresh token, which are database validated and we need to just refresh once for 100s of calls in 5 minute.
> "stateless"
Yeah this sounds bad and I don't think people do it.
> JWTs which just store a simple session token are inefficient and less flexible than a regular session cookie, and don't gain you any advantage.
Ok, but it is not worse too.
> The JWT specification itself is not trusted by security experts. This should preclude all usage of them for anything related to security and authentication. The original spec specifically made it possible to create fake tokens, and is likely to contain other mistakes. This article delves deeper into the problems with the JWT (family) specification.
What are they even trying to say?
But yes, short life times with frequent renewals is necessary; that's obvious though. And same applies to any other auth tech.
> they are not secure.
They are secure if they fit your risk profile, a blanket statement like this is just disinformation.
Don't treat your peers like idiots.
The post is not descriptive enough
It should explain how to not store JWT instead of just saying JWT is bad.
> The JWT specification is specifically designed only for very short-live tokens (~5 minute or less). Sessions need to have longer lifespans than that.
Your auth token should be 5 or 10 minutes long, yes. Your session should be encoded in a refresh token, which can be another JWT or completely opaque, whichever you prefer.
> "stateless" authentication simply is not feasible in a secure way. You must have some state to handle tokens securely, and if you must have a data store, it's better to just store all the data. Most of this article and the followup it links to describes the specific issues:
No. Stateless auth is completely feasible in a secure fashion. You need short lived tokens as stated above, and to perform instant logout you need to be able to identify JWTs that were invalidated at the level of your API gateway. You don't need to store every JWT that is valid, only a way to identify the ones you want to invalidate early. If you make bulk invalidations not go onto this store (and just accept that with bulk logout, users will remain logged in for up to 5 minutes, which if you're doing logouts in bulk, this does not matter to begin with), then this typically fits in memory. "if you must have a data store, it's better to just store all the data" is just wrong.
And of course for refreshing the token, you go to your data store and recompute things. But the point is to do this every 5-10 minutes like the author (correctly) identified, not on every API request.
> JWTs which just store a simple session token are inefficient and less flexible than a regular session cookie, and don't gain you any advantage.
But no one does this. People use JWTs as auth tokens which are short lived but contain a bunch of information about the users that you'd get in huge trouble if it could be forged (user ids, resolved geolocation, entitlements, cohorts, etc etc), but that you also don't want to make every service of yours look up against a data store or compute for every single API request. It should be data that normally doesn't change during the duration of the auth token. When it does change, you should have a mechanism to immediately invalidate the previous one (see above) and issue a new one on the API call that made the change. Simple.
> The JWT specification itself is not trusted by security experts. This should preclude all usage of them for anything related to security and authentication. The original spec specifically made it possible to create fake tokens, and is likely to contain other mistakes. This article delves deeper into the problems with the JWT (family) specification.
Yes, all standards are horrible if you look at them long enough. XML is terrible, YAML is terrible, ASN.1 is horrific. I'll take a flawed standard that has been studied and criticized publicly so I have libraries and know where the footguns are vs rolling everything myself and having to find out the footguns on my own.