

Join the discussion
Write your take first — we'll ask for email only when you're ready to publish.
- Hacker News
- Been in this rabbit hole since JWT shipped. As others have mentioned, cookies have their own risks, you're now juggling both XSS and CSRF, and the CSRF defenses (SameSite, tokens) do nothing against XSS since that's a same-origin attacker.
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.
by eranation - > Just please for the love of all that's good and pretty, don't store a JWT in a httpOnly cookie.
Depends on who is saying, I've read the same thing but the other way around. Never store a JWT in LocalStorage and always store it in a httpOnly cookie.
by Maxion - Hmh, the way I usually use JWTs is as an authentication cache. You obtain your authentication token from the auth service which grants you permission to other services.
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.
by himata4113 - irrevocable* cacheby iririririr
- At my last work we used to use "the client obtains the authorization token from the auth service, and supplies it to other services" too, but at some point we found that a) permissions in the JWT grew a tad too large to reliably pass them in the HTTP headers; b) localStorage is finicky, as is reliably refreshing it — Safari on MacOS apparently turns off the JS timers if the user looks away from the open page; c) the client can actually see how we represent permissions internally; d) the client only really ever faces a single most popular service anyhow; e) that most popular service used only JWT for authentication as well, so stealing a JWT token was a problem.
So we switched to that main service obtaining the client's JWT itself from the authorization service, and then handling refreshing it on its own. That means that if the client e.g. buys some new feature, they still need to refresh the page (so the new connection to the main service is made) to see it working, but it's always been this way even before, so... eh. We had to scale the auth service a tad, but other than that, it worked fine.
by Joker_vD - I'm right now adding rabbitmq for notification pushing to a website. Using JWT authentication to control where and what clients are allowed to read, with short lifetimes and regular token refresh.
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.
by Grollicus - Completely, for some things it’s okay to trust the JWT. Nobody is saying let people transfer money or view government secrets with only a JWT and no security in depth. However receiving and potentially sending ephemeral communications seems fine, nobody got hurt in the 30mins the stolen token was valid for!by andy_ppp
- OAuth2 has been the web security standard for ten years. I don’t recall any major security announcements about it. The OP is confused.by ChicagoDave
- I'm confused. What does OAuth have to do with JWTs? They seem orthogonal: OAuth2 doesn't specify a format for access tokens, nor does it require statelessness.
- =0 I stumbled across this post and was thinking that it's interesting to see this topic trending now, since I've done a lot of work on it in the past. Then I clicked through and realized the author is linking to some of my stuff! What a blast from the past.
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!
by rdegges - Invalid certificate - dark humor.by tored
- JWTs are awesome but they are are being overused by people. People use them in web, on mobile and everywhere in between. Places where cookie or bearer token auth has already solved the problems. JWTs strength is that it can be verified independently without real-time coordination between services. So a service can issue a JWT with auth scopes to a user. The user can take the JWT to any other service and if that service trusts the signer, it'll allow the user access and also get basic user info from the token itself. The service doesn't need to make an API call to the issuing service or even know if/where it exists. That is where JWT is really powerful.
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.
by owaislone - > Servers must ... make DB calls to verify the user is still active
I'm not being facetious, genuinely asking - is this a big deal? Should be a pretty cheap query, and with pooled connections, hardly any overhead.
by carter2099 - JWTs are fine, seems a bit sensationalist title...
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)
by blixt - Okay, so hack into a site that uses JWTs for login, if it’s so insecure we should be seeing loads of attacks against them right? Stolen tokens everywhere being used to impersonate people and other things. For example I believe ChatGPT is using Auth0 which uses JWTs, so you can hack this insecure token system? Should be easy right given the extremity of the warning that JWT is the big problem here.by andy_ppp
- by yencabulator
- Finally, feels like the blog stirs up unnecessary dramaby mawadev
- A lot of these type of coding practice debates are theoretical. IMO coding is more-or-less fancy blue collar work. What matters is what works in practice, not what works in theory.by Maxion
- In sessions vs. JWT revocation lists, there is an argument in favor of JWT revocation lists. JWTs have a limited expiry timestamp, so you only ever need to maintain a revocation list for tokens not expired yet. Given that you probably only have a fraction of JWTs revoked compare to valid JWTs in circulation, you only need to query a very small dataset for each request.
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.
- > JWTs have a limited expiry timestamp, so you only ever need to maintain a revocation list for tokens not expired yet.
Sessions have expiration timestamps too, and you can configure them however you like.
- Session data lookup is one select to database that gives 0..1 rows and uses index. In most cases this is not something you need to worry about.by jpalomaki
- The moment you have to look up the user object, you've lost the primary advantage of JWT, and might as well ditch it.by vidarh
- This links to some other blog post for the bulk of it's 'why', and that blog post mostly seems to be annoyed about "You cannot invalidate individual JWT tokens". Which every time I've implemented, the general guideline is to check for invalidated nonces somewhere. Which resolves that random blog posts second point too.
>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.
- > "You cannot invalidate individual JWT tokens". Which every time I've implemented, the general guideline is to check for invalidated nonces somewhere. Which resolves that random blog posts second point too.
100% agree. This is common sense to me and I'm always surprised to re-learn people don't do this
by jotato - Right, but once you're checking for invalid nonces, your token format is now stateful; it's lost the primary benefit of statelessness, which is continuing to function under network partition between the application server and the token state store.by tptacek
- Also many situations just don't require a "Logout" button and hence don't require a revoked list.
On a linked page, there's also this:
> Any JavaScript code on your page can access local storage: it has no data protection whatsoever. This is the big one for security reasons (as well as my number one pet peeve in recent years).
This is a weak argument. You know, just don't put "any javascript code" on your webpage? Limit it to trusted javascript code? If you allow random people putting random javascript on your webpage, you have already lost anyway!
by tasuki - Yeah... some early implementations just allowed for any authority to be set in the header and trusted it... that's of course wrong from the start... if you only allow for trusted or "known" authorities a lot of the contextual concerns become non-concerns.
Beyond this, you can make shorter lived JWTs just fine in the browser and have the agents self-update. If you use Azure Entra or a number of other providers it works this way in practice... you keep your JWTs relatively short lived (5-15m) and can even check for jti revokation.
JWTs are incredibly useful for separating/reusing an access authority from your applications/api systems. You shift the attack surface and do it in a way that can be trusted. We use PPK for lots of things, including SSH all over the world. No, I wouldn't use shared secrets and I wouldn't use long lived tokens... but short lived, ppk signed tokens from verified/known sources are generally fine.
For that matter, it's often API keys that are really problematic. Just had to implement them... for me, the API key presents as a Bearer token as well, but there's a short "sak." prefix then an identity part (base64url uuid bytes) followed by a secret as base64url bytes... in the database is the uuid and a passphrase level salt+hash from the secret.. so the api key generated should be treated as a secret and is one-way to the database, so a db breach doesn't breach auth.
Even then, an API key leak is far mroe likely than a problem with a well implemented JWT solution.
by tracker1 - JWTs are insecure... even when using trusted, rsa/ppk based signing methods? not shared secrets.
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.
by tracker1 - You can make a JWT invalid after 30 seconds or even 1 second. You should set an aud (audience) when creating the JWT. Otherwise the signature is crypto-graphically sound. Validate every single JWT every single time with a short lifetime.
OIDC tokens are all JWTs btw.
by hparadiz - They aren't insecure really. They're pretty secure by design but people are using them for things they were never designed for like web/mobile client auth.by owaislone