Stop Using JWTs

Posted by dzonga 16 hours ago

Counter374Comment215OpenOriginal

Comments

Comment by solatic 14 hours ago

Necessary qualifier: for browser-based user sessions.

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

Comment by RagingCactus 14 hours ago

> Necessary qualifier: for browser-based user sessions.

> Plenty of good uses for JWTs for service-to-service communication.

This is the sensible conclusion right there. I agree JWTs are the wrong tool for the use case of user sessions in the browser.

To give some more arguments:

All the signature and encryption stuff in JWTs is complex. While common JWT libraries have now mostly got their stuff together, this has not always been the case. There were plenty of libraries accepting the "none" algorithm [1] or allowing attackers to forge tokens by using a public key as a shared secret [2]. This is the direct result of the complexity criticized in the linked blog post.

JWTs also cannot do some stuff you want for user sessions. You can't invalidate them without keeping a revocation list somewhere. But if you have to check an identifier for revocation on every request you could just use an opaque session ID and look that up on every request instead! Sure, you can use short-lived tokens and refresh them all the time, but why bother with that for a typical application that has to keep some state anyway?

All that being said, I wholeheartedly agree that there are use cases in distributed systems and machine-to-machine communication where signed tokens can be useful. Just please don't confuse the two cases.

[1] https://nvd.nist.gov/vuln/detail/cve-2022-23540

[2] https://nvd.nist.gov/vuln/detail/CVE-2024-54150 (just a random example from googling, I don't know what library made this one infamous)

Comment by nine_k 13 hours ago

> if you have to check an identifier for revocation on every request you could just use an opaque session ID and look that up on every request instead!

One reason could be the size. A revocation list only needs to keep session IDs of recently logged-out sessions, for which the token's TTL hasn't yet expired. It may be a much smaller list than a list of every active session.

Also, a JWT (or a Macaroon, etc) can store a large amount of details about the session in a cryptographically secure, unforgeable way. This rids you of the necessity to store all that in your active session database, again cutting the size.

Comment by agwa 13 hours ago

As someone who operates a PostgreSQL database containing 27 billion SSL certificates, each 1-2kb each, with a bunch of secondary indexes that get inserted in random order, I find it pretty incredible that people see the need to optimize their session database. At what scale does the size of the session database actually matter?

Those stateless tokens may be "unforgeable", but they are replayable, and if you're not mindful of that you can have security vulnerabilities.

Comment by mewpmewp2 11 hours ago

I think one meaningful case is when you have services in very different locations and you would rather than having to make a request to a session store in a single location, replicate the data to each location for better latency, so in this case a revocation list.

Comment by hparadiz 13 hours ago

You should do some basic optimizations. Fixed length table and indexes on the unique string for fast lookups. I also like to do a rolling delete for old sessions after 30 days unless mobile session that is logged in. Those get to live forever.

Comment by agwa 13 hours ago

Fair enough, but those optimizations are basically free. People think stateless tokens are free but they really are not.

Comment by hparadiz 12 hours ago

The cost of the stateless token is basically the CPU usage for signing the message and checking the signature with the public key on the client. Example: Google Compute Instance asks metadata server for OIDC token (which is a JWT). The metadata server respond with the token that basically says "here's the machine service account, here's the machines ID, this token is proof that I am service account abc123 and it's valid for 20 seconds". This is one of the most common uses of JWTs in enterprise. You don't store them. They actually are free.

Lots of web devs get tricked into using them as primary session tokens and it's a huge anti pattern. I see it all the time and people get aggressive about it.

Comment by agwa 12 hours ago

The cost is the vigilance required to use them safely. It's not just compute/storage costs.

Comment by hparadiz 11 hours ago

I didn't downvote you. You're absolutely right. Implementation of anything is work.

Comment by locknitpicker 2 hours ago

> Fair enough, but those optimizations are basically free. People think stateless tokens are free but they really are not.

Strawman.

The only requirement for a JWT is posting the JSON Web Key set with the public keys used to verify the JWTs signature. That's the full cost of a no-frills JWT implementation of you exclude IAM.

If you want to have one-time JWTs you need to maintain a revocation list. This is literally a set of IDs. If you go nuts and use GUIs for JWT IDs that means each entry takes as much space as 4 ints, and all you need is a set membership check on said integer. Even at FANG scale you can handle that scale in a memory cache service such as ValKey running on a COTS desktop.

Now show us your alternative.

Comment by dns_snek 38 minutes ago

> If you want to have one-time JWTs you need to maintain a revocation list.

No, you always need a revocation list if you want to handle user sessions in a secure manner. What claims do your tokens contain? If it's anything other than some stable identifiers, like user name, email, permissions, etc. then you now have a cache invalidation problem.

But if all your token carries is an identifier which you need to look up, how's this any better than a signed cookie containing the session ID? All you've done is add complexity.

Comment by lmm 5 hours ago

What do you do about availability? AFAIK the choice is to pick one of 3 or 4 hacky difficult-to-administer clustering solutions, or have that single PostgreSQL database be a SPOF for your whole system.

Comment by nish__ 3 hours ago

27 billion? Do you work at AWS?

Comment by stickfigure 9 hours ago

The issue isn't size, it's load.

Comment by saganus 10 hours ago

I am still waiting for Macaroons to be used widely. I think they are a fantastic invention.

It seems they were not of very much use in the past, but with the agentic-everything now, I see this as a great way of delegating permissions to subagents, third-party agents, etc.

Working on something along these lines but unfortunately I cannot dedicate as much time as I'd like.

Still, if anyone is reading, give Macaroons a try!

Comment by tptacek 6 hours ago

We have what I believe to be one of the world's largest deployments of Macaroons. They're a mixed bag, though I think they're a lot more interesting in a world where agents do most of the fiddly work.

https://fly.io/blog/operationalizing-macaroons/

Comment by saganus 4 hours ago

I am very aware of your work!

It's the only prod usage of Macaroons I know of, I think.

Third-party discharge seems like a great way to have human-in-the-loop gating, among other interesting things.

Would be great reading your thoughts if you ever write about the agentic use case, having all the fly.io experience

Comment by malfist 6 hours ago

I like the raspberry ones. Or lemon is also good

Comment by jiveturkey 9 hours ago

JWTs can do that (delegate) and such capability is already well defined.

Comment by saganus 8 hours ago

Maybe I stated it wrong. Macaroons have the ability to attenuate the restrictions _without_ contacting the auth server, which makes it IMO fit for restricting and attenuating as much as you want, without much cost.

If I need a roundtrip to the auth server to attenuate, I am not necessarily going to do it as often.

Comment by tptacek 6 hours ago

Most token formats delegate. Macaroons support attenuation, confinement, and embedded third-party claims, none of which are JWT capabilities.

Comment by robertlagrant 12 hours ago

> While common JWT libraries have now mostly got their stuff together, this has not always been the case. There were plenty of libraries accepting the "none" algorithm [1] or allowing attackers to forge tokens by using a public key as a shared secret [2]. This is the direct result of the complexity criticized in the linked blog post.

I'm a bit surprised at this. These are extremely simple to solve - the first time I ever did a JWT-reading implementation I specified the right defaults, which are very simple, even for a mid-level backend person I would say, and they haven't needed changing in 8 years or whatever it's been. It really isn't very complex.

Comment by agwa 12 hours ago

You would think so, but even an authentication company screwed it up:

https://cybercx.co.nz/blog/json-web-token-validation-bypass-...

Comment by userbinator 4 hours ago

Clearly trying to be too general. I wrote a tiny JWT validator before that only allows a very small subset of algorithms because I wasn't expecting the JWTs it would handle to have anything else, and obviously not "none".

Comment by y2244 9 hours ago

Wow lol

Comment by jongjong 3 hours ago

The people who are upset about JWTs probably got burned by trying to use them in a weird way. Some people try to store sensitive data inside JWTs... WTF, the idea would never have entered my mind! Sensitive data should stay on your server. Encrypted or not! JWTs are supposed to be signed, not encrypted! You shouldn't even think to put sensitive info in there. Also, WTF is wrong with people who accepted algorithm "none." Most of these people who tried to tweak the defaults had no idea what they were doing in the first place.

Comment by RagingCactus 2 hours ago

> Also, WTF is wrong with people who accepted algorithm "none."

They dared to use the default validation function of their JWT library. They did not choose to accept "none".

And the library authors implemented it because it's in the spec. It doesn't excuse that the default was to accept "none", but it is an explanation and in my opinion a valid critique of the standard.

Comment by jongjong 2 hours ago

Yeah well it's definitely a footgun built into the spec but ultimately an library implementation mistake.

Comment by unscaled 1 hour ago

I think both you and GP are somewhat misrepresenting the OP is saying. OP's argument is three-fold:

1. JWTs are not a good fit for a session token (although there are several RFCs that are trying to shoe-horn JWTs into this use).

> TLDR: JWTs should not be used for keeping your user logged in. They are not designed for this purpose, they are not secure, and there is a much better tool which is designed for it: regular cookie sessions.

2. JWTs have other "valid" use cases that only need a very short-lived token (e.g. a transit token or a request signature) and don't need to care about user authentication, revocation, XSS etc.

3. But JWT should not be used even for the "valid" use cases, since you have better (read: less outrageously insecure) alternatives nowadays.

> Also note that "valid" usecases for JWTs at the end of the video can also be easily handled by other, better, and more secure tools. Specifically, PASETO

You've noted these issues yourself. There are many common vulnerabilities with JWT: alg=none, algorithm confusion and weak key brute-forcing, mandating weaker algorithms like RSA and ECDSA while making the best, fastest and easiest to implement algorithms like EdDSA "optional".

There are also other design deficiencies that JWT makes by trying to be a generic cryptographic envelope format rather than a token format: e.g. expiration can be omitted and this feature that caused some libraries to not verify expiration by default or have a different (and confusing) set of token parsing methods that do not enforce the expiry. PASETO is a better design that is secure against all of these issues. Sure, there are a few minor qualms I can find with PASETO (e.g. no mandatory key ID and no support for non-JSON payloads), but it's unlikely to face the same avalanche of CVEs we got with JWT libraries.

Comment by 0x696C6961 9 hours ago

The design I've landed over the years is to use both. The cookie is a session token and that's where you handle refresh tokens. Then there's an endpoint where you can mint a short-lived tenant-sepecific JWT. This holds the scopes & tenant id. The session token only lets you access the web assets & mint JWT tokens.

Comment by chuckadams 11 hours ago

A revocation list defeats the purpose of JWTs. If you find yourself needing one, JWTs were probably the wrong choice to begin with.

Comment by jongjong 3 hours ago

Not really. Being able to verify a user's signed identity immediately without having to do any database or memory store lookup first is valuable in itself.

With session IDs, anyone can spam/DDoS your system much more easily; they don't even need to be authenticated to waste your computing resources as they can send plausible-looking session IDs and your system will waste a ton of resources querying your session store or database to figure out that the session doesn't exist... It adds a ton of latency and wastes CPU cycles across multiple systems. Also stateful systems like Redis are harder and more expensive to scale than stateless systems like application servers. Not to mention that they may be depended upon by other parts of the application so hitting those too hard can be more disruptive. And that's kind of best-case. Some people use a database to store their session data...

At least with a revocation list with JWT. If the JWT says that the user is user1234, then you know that this is a real, previously logged-in user, they have an account at stake, you can afford to spend a bit of time/resources to check them against your revocation list... And if they are on that list, you can ban their ass and they're done! They'd have to create a new account of they try to spam you again. They can't spam without a valid JWT and they can only get that by authenticating with your server first.

Sure with JWT, some computation is spent on verifying the signature but it's very cheap using the default algorithm and only touches one system... And you only need to call another system once you know that the user is valid.

JWT is much more robust for DDoS prevention because of this. But yeah, you don't necessarily need a revocation list. You can use short JWT expiries with frequent token refreshes. Revocation list is good if you need immediate ban.

Comment by hasley 2 hours ago

Can't a system be DDoS'ed with wrongly signed JWTs as well?

Is signature checking (much) cheaper than finding an opaque session ID in a database?

Comment by jongjong 2 hours ago

Yes but it only impacts your stateless app servers which are easier to scale. Your backend services/stores are protected and not affected by the attack.

Comment by locknitpicker 2 hours ago

> All the signature and encryption stuff in JWTs is complex. While common JWT libraries have now mostly got their stuff together, this has not always been the case.

This is a red herring. Applied cryptography was never considered an easy subject in software engineering circles. Neither was algorithms and data structures. Yet, it's still a basic tool, and developers are still expected to understand things such as why some maps allow reads in constant time while others require log time.

Some libraries being buggy never was an argument against using libraries. And do you expect your single-purpose code not to be?

I think we are seeing in this thread a knee-jerk reaction against perceived complexity. Yet, if you sit down and compare JWTs with alternatives and list all features along with pros and cons, you'd be hard-pressed to even try to put together a case for JWTs being bad.

Comment by LgWoodenBadger 12 hours ago

Come on, it’s not like the two are even within the same magnitude or three

“But if you have to check an identifier for revocation on every request you could just use an opaque session ID and look that up on every request instead!”

Comment by hparadiz 13 hours ago

If you don't understand conceptually how to verify a signature with a public key the very first thing you should do is get that working and then work from there. It's completely unacceptable to ship without this.

Comment by figassis 10 hours ago

Comment by dchest 10 hours ago

WTF:

> Each user has a secret: Stored securely in the database.

> Stateless Validation: The core validation remains stateless. We only need to consult the database for the user's secret, which we'd likely do anyway for authorization checks.

Is "stateless" the same as "serverless" now? Is author's brain stateless?

Comment by figassis 9 hours ago

A JWT is usually signed, with a secret you keep in your app. The statelessness of JWT is that it contains all the information you need to verify it. You do not need to ask a db if the token is there and valid.

Storing a user's secret, the same way you store your applications secret does not make it more or less stateless.

In since you now have 2 layers of protection, you don't actually need to verify agains a user's secret immediately, you simply need to check that the token is valid using the app secret. The subset of valid tokens that you need to check is much smaller than the universe of all the unexpired tokens your application has issued.

If you have a security incident and need to revoke tokens for only a subset of your users, now you don't need to rotate your app secret and invalidate every single token and break every single session. You can simply log those users out.

Is author's brain stateless -- my bad, I thought this was not reddit

Comment by dualbus 5 hours ago

> [...] you don't actually need to verify agains a user's secret immediately, you simply need to check that the token is valid using the app secret. The subset of valid tokens that you need to check is much smaller than the universe of all the unexpired tokens your application has issued.

What you are describing here is different than what is described in the blog post that you linked to.

Please look at the definition of the function 'validateToken'. In particular, notice how 'getUser' function (which the author notes issues a DB query) is called for every JWT with a valid signature!

EDIT: I failed to realize that you are the author of the blog post. Still my point stands, in that your description doesn't match what the code does.

Comment by dchest 1 hour ago

Being able to quickly reject invalid sessions identifiers is a useful property in some cases and is normally done by authenticating stateful session tokens with a MAC using a global app key. This can be used for DoS protection if the cost of a database lookup is more than the cost of MAC, and the complexity is justified. It ensures that the random numbers a user is trying to present as their session token are the numbers generated by the server if the key is not leaked.

Because you're trying to bolt things on top of JWT, you're creating a worse version of that stateful authentication pattern:

1. You lost the statelessness of JWT by making database queries. Your claim that "you don't need to verify against user's secret immediately" is false, as you need to do that in all cases immediately after verifying the JWT signature to get the benefits of your system (token invalidation). Sure, you reject completely invalid tokens early, but you still need the statefulness to authenticate users properly (if your goal is to be able to invalidate tokens).

2. In your version, getting a read-only access to the user database (leaking per-user secrets) completely destroys token invalidation, and all your authentication now depends on one key. If, in addition to that, the JWT signing key leaks, user authentication is completely destroyed and can be bypassed by the attacker, who now can sign in as any user. (A common way to leak all this is by failing to properly secure backups).

Compared to a stateful session system with split-tokens, where the database stores tokenId => verifier, where verifier is Hash(randomToken), and user's token is id||randomToken, read-only access to the database doesn't let the attacker authenticate as any user. If the tokens that users presents are in the form of id||randomToken||HMAC(serverKey, id||randomToken) for early rejection as above, leaking serverKey still won't allow the attacker to authenticate as any user. The attacker needs write access.

> Is author's brain stateless -- my bad, I thought this was not reddit

I didn't realize that you were the author, I thought you were a reader who was misled by this blog post. Even better: you can go and edit it, removing "stateless" everywhere! It's fun to invent various protocols, but when someone points out the errors, surely you'd want to fix them -- no shame in making mistakes if you correct them.

Usually, when I think of a protocol, after writing down "Benefits" (as in your blog post), I write "Drawbacks" and then try to come up with downsides and compare it with existing protocols. I'd suggest you do the same.

PS. Find yourself in this picture: http://cryto.net/%7Ejoepie91/blog/2016/06/19/stop-using-jwt-...

Comment by what 5 hours ago

> Common workarounds like maintaining a blacklist of revoked tokens introduce statefulness, negating the benefits of JWTs.

> Validation: On each request, we validate the JWT's signature using the application secret and then validate the sjti using the user's secret.

Having to lookup the user secret from the db is no different than consulting a list of revoked tokens. You claim consulting a list of revoked tokens to be stateful. How is looking up the user secret different?

Comment by stavros 2 hours ago

If I need a database query to validate the token, it's not stateless.

Comment by RagingCactus 10 hours ago

> First, we need to add a token_secret column to our users table:

> ALTER TABLE users ADD COLUMN token_secret;

So it's "stateless" but we have to query the users database on every request? How is that more stateless than SELECT * FROM session WHERE id = cookie?

Ignoring that and taking the mechanism as given: Why the obsession with cryptography, in this case HMAC? I don't see any reason why another signature is needed here when I believe the same outcome could be accomplished with a token_epoch field in both the signed JWT and the users table. Just increment the epoch to revome old tokens. Or even better, drop the epoch field and have an iat_not_before field per user. The field in the JWT is signed, the whole point is that you can trust it.

Do let me know if I miss anything here please. Assuming I haven't: it's always puzzling to me to see people being so eager to sprinkle more cryptography on anything that is supposed to be secure. For me, I've become more afraid of cryptography the more I learned about it. Cryptography is hard. It's not a magic ingredient for security. At best, it's dangerous black magic -- very potent, but pronounce a single syllable of your magic spell wrong and it _will_ blow up in your face.

Comment by figassis 9 hours ago

You don't actually have to do a db trip to get a user secret and revoke a token. A token comes in, and you can store the secret in the same place you store your application secret. Because you do need to store it, cache it, whatever. The point here is you no longer need to keep a revocation database of every token you issued that is still unexpired. Just rotate the signing secret and every token issued until then will be revoked. Goes from maintaining millions of tokens to maintaining a smaller cache of user secrets that are probably rarely updated.

Why not an epoch? because this gives control to the user. They can now logout regardless of token ttl. The point is not obsessing over crypto, JWTs are a cryptographic solution, it's what makes them stateless and I have nothing agains cookies or any other session token. I use them interchangeably.

My pain point was that whenever I needed to use a JWT or whenever I worked a company that used JWTs, their main frustration was "oh but then we can't revoke them easily without maintaining a revocation list". Well now they don't have to.

Telling them just migrate to "this or that technology" is not how this works.

Comment by what 4 hours ago

What? So instead of storing a revocation list, you store a per user secret that you need to consult. What is the difference? How is that stateless? How does it avoid a “rb round trip” (where are you storing the user secret)?

Comment by catlifeonmars 7 hours ago

Wouldn’t it be simpler to use a session token? This complex machinery does nothing but look fancy.

The application secret is redundant if the per-user secret is used.

Also I’m inferring from the article that the author is using symmetric keys (HS256) for their JWTs. In what world can you securely distribute symmetric keys but can’t use an opaque session token?

Comment by throwaway7783 10 hours ago

"We only need to consult the database for the user's secret..." , which kinda defeats the purpose.

Comment by unscaled 52 minutes ago

Wow, Fortune 500 companies are using an insecure technology, get hacked and exploited by cryptominers and PII burglars and then just patch their vulnerabilities and call it a day? This never happened before! /sarcasm

Just because a certain practice is popular, doesn't mean it's good for security, and it definitely does not mean the companies who do this never get hacked. Popular != Unhackable. I don't believe this needs to be stated.

Cases in point:

- Passwords limited to 8 characters

- Passwords hashed with a fast, single-iterated hash (with or without salt, that's not the main point, we are not in 2003 anymore goddamnit, and GPUs are a thing!)

- Passwords stored in cleartext

- Using old-style C/C++ without bounds checking and fuzzing and treating stack overflow exploits as just a fact of life we'd have to live with, while most other languages don't get anymore (and if you have to use C/C++ for reasons there are ways to prevent this).

- Injecting unverified user input directly into SQL strings.

- Using ancient software without ever patching or updating vulnerable versions.

Comment by kyrra 14 hours ago

JWT used to be bad due to libraries with poor defaults. Downgrade attacks were fairly common a number of years ago.

Since most of the common libraries across all languages have gotten more sane defaults, it actually is pretty secure nowadays.

Comment by unscaled 1 hour ago

JWT libraries had poor defaults because the spec was poorly designed.

Of course JWT can be implemented securely. Even XMLDSig can be implemented securely. But if the spec is not designed with security and misuse-resistance as a tier 1 priority, you will get more issues. The fact that we didn't see the same sheer volume of issues with PASETO or macaroon libraries (admittedly, the later are far less numerous). I can find only one CVE for a PASETO library from 2020, and this is an issue that has nothing to do with the algorithm itself (JPaseto < 0.3.0 switched the order of two arguments in their hash function call, generating weaker hashes).

The reason PASETO won't have the same issues as JWT is the design (especially with v3/v4). There is no alg=none, symmetric keys are fixed size (so no weak keys can be used) and algorithm confusion is prevented by an explicit implementation guide[1] that strongly mandates that keys for different algorithm version have different types, and verification functions MUST reject a key of the wrong type.

Is JWT safe now? Maybe. A lot of issues have been fixed, but new issues keep coming all the time. We're not even halfway into this year and I can count at least the following serious 2026 CVEs: CVE-2026-28802, CVE-2026-29000, CVE-2026-1529, CVE-2026-22817/8, CVE-2026-34950, CVE-2026-23993, CVE-2026-32597, just to name a few. Most of them are the same classic alg=none, signature verification bypass and algorithm confusion issues.

The issues is that new libraries are coming all the time and the vulnerability elimination process for existing libraries is just a random scattershot. If a security researcher has happened across a vulnerability in library X and reported it, it's solved. If nobody has found it yet: though luck. Unless you pick a library that has been officially audited for these issues, you don't really know if it's truly safe. If you use a PASETO library, it's probably not audited either, but the chance of it having these common types of issues (and other issues, like psychic signatures[2]) are close to nil.

---

[1] https://github.com/paseto-standard/paseto-spec/blob/master/d...

[2] https://www.securecodewarrior.com/article/psychic-signatures

Comment by tptacek 12 hours ago

If we stipulate that, we're still left wondering what the utility is of a standard that creates affordances for the insecure defaults, as opposed to just designing it right from the beginning.

Comment by jeswin 8 hours ago

> utility is of a standard that creates affordances for the insecure defaults

You could make the same argument about Cookies.

> as opposed to just designing it right from the beginning

And generally, it's quite difficult to design it right from the beginning because one would often start with the wrong assumptions. Most standards evolve, and it should be acceptable.

Comment by tptacek 7 hours ago

No, that doesn't square up. It's like arguing "you could say the same thing about TCP, because it allows you to build JWTs, which are a bad protocol".

Comment by jeswin 3 hours ago

Not the same. HttpOnly/Secure cookies were added much later and was not the default. They should have been inaccessible to JS by default from the beginning, and this policy has been a source of countless attacks.

Comment by unscaled 6 minutes ago

If memory serves me right, cookies were designed by Netscape in 1994 before JavaScript was even a thing. They were released in an early beta of Netscape (0.9 something), while Javascript was only added in Netscape 2.0. SSL 2.0 was only added in Netscape 1.0. So the HttpOnly/Secure attributes were not relevant to the first cookie design, which wasn't even a standard.

When the first cookie standard (RFC 2109) was released, the Secure attribute was added. You could argue they missed HttpOnly, but JavaScript itself was highly non-standard and underspecified mess during this period (and for a while later too). Almost nobody was thinking about XSS as far as I can tell, and that term was probably only coined at least 2 or 3 years later (by Microsoft researchers[1]). At 1997, the people who even considered XSS, probably only saw this as an HTML injection issue that can be fixed at the injection site and doesn't require any special protections against JavaScript code.

If you really want to point a finger at issues in the early cookie design, then you could talk about domain matches. Not making the port part of the matched domain and not allowing an explicit way to trigger an exact domain match with a specified domain was a mistake. And the confusing leading dot rules (probably for optimizing a substring match without parsing the domain components) was also a mistake.

But RFC 2109 was replaced with RFC 6265 and now RFC 6265bis (which is not released yet, but is mostly implemented by the big browsers). These RFCs fix most of the big issues we had with cookies, and do not shy away from breaking existing behavior: setting SameSite=Lax as the default and restricting SameSite=None to secure contexts broke A LOT of sites for improve security. The changes made in RFC 6265 to forbid multiple cookies in one header also broke many sites.

An equivalent approach in the JWT spec would be a new RFC that does the following:

1. Forbids implementing Unsecured tokens and removes alg="none".

2. Removes RSA and ECDSA (or at least deprecate them) and make Ed25519 the "Recommended+" signature algorithm. If we allow RSA and ECDSA for compatibility, they must be explicitly enabled with feature flags or some other marker that signals their insecurity and security advisories on their potential vulnerabilities must be attached.

3. Removes the entirity of JWE as it is currently implemented (to be replaced with encryption else that is NOT orthogonal to authentication).

3. Requires that HMAC secrets are specified as binary base64url data. They SHOULD be generated by a CSPRNG or a derived using a safe derivation method (such as HKDF-SHA256 with safe key material) and MUST be at least as long as the "security size" for the algorithm (i.e. 32 bytes for HS256, 48 bytes for HS384 and 64 bytes for HS512).

4. Makes the "exp" claim mandatory to set, and mandatory to verify.

5. Add a section with strict implementation guidelines for libraries, e.g. `parse()` functions that skip verifying the token should have a clear name like `inspect_without_verification()` or `dangerously_parse_without_verifying()` and `verify()` function should always receive a key that is strictly typed to a specific algorithm.

6. Remove or restrict the usage of fields that allow the JWT sender to dictate the keys used for verifying it, like "jku" or "x5c". For instance, the standard can mandate that when implementing these fields, the verifier MUST NOT accept any JWKS URLs that do not match one of the explicitly allowed patterns or X.509 certificates that are not signed by an explicitly trusted CA.

[1] https://www.youtube.com/watch?v=mKAWpFdVcPY

Comment by ForHackernews 10 hours ago

Spec writers and library authors are human? Who knew

Comment by unscaled 4 minutes ago

PASETO and TLS 1.3 were also written by humans. TLS libraries (which are several orders of magnitude more complicated than JWT libraries) are also written by humans.

If you passionately care about security and misuse-resistance you CAN write a spec that will lead to fewer implementation issues.

Comment by tptacek 10 hours ago

I don't understand what this is meant to communicate. The standard is either good or it isn't. "Good effort" is not an engineering assessment.

Comment by ForHackernews 10 hours ago

Your objection is that they should be "designing it right from the beginning" but that applies to all realms of endeavour. The reason they didn't is human frailty.

If everyone simply designed everything right from the beginning we would live in nirvana.

Comment by andai 9 hours ago

I read an article about business which had this classification, "Would be weird if it worked", "Might work", and "Would be weird if it didn't work" and argued that you want to be in the last category.

In engineering we aspire to a slightly stronger standard: "I made it physically impossible to fuck this up."

Comment by tptacek 10 hours ago

You've completely missed my point. I don't even accept the premise of the JWT standard. But the eventual migration to safer default settings, in a format that continues to expend implementation effort to support settings nobody should use, is in fact a practical engineering problem with the standard.

Comment by ForHackernews 10 hours ago

And they've published updates[0] and libraries have hardened their defaults and removed support for insecure values (e.g. alg='none'). I'm not sure what more you want?

I'd rather use a refined, battle-tested standard with lots of eyes on it than some new untested contender produced by a handful of upstarts ("look, we just designed it right from the beginning! This time it's perfect!") PASETO reeks of second-system syndrome.

[0] https://www.rfc-editor.org/info/rfc8725/

Comment by tptacek 9 hours ago

I don't recommend PASETO either.

Comment by doc_ick 8 hours ago

What do you recommend then? What technology has been designed, completed, then used for years without any updates or problems?

Comment by kasey_junk 7 hours ago

Bearer tokens are a dead end? You have to validate them anyway so traditional auth is the fallback.

Comment by tptacek 7 hours ago

https://fly.io/blog/api-tokens-a-tedious-survey/

tl;dr: most of the time you should use opaque random strings.

Comment by ForHackernews 5 minutes ago

API tokens are a very small narrow part of the authorization universe. Having a shared secret relies on a trust relationship between the resource server and the identity provider that does not exist between, say, my SaaS backend and Google or Meta's login system.

Comment by henryoman 3 hours ago

Not for long

Comment by swiftcoder 1 hour ago

> Please, if JWTs are such a horrifically insecure standard, go ahead and publish your means for hacking AWS STS's AssumeRoleWithWebIdentity

The standard and AWS' specific implementation thereof are two different things. Can you afford a security org the size of Google or Amazon's security orgs? If not, you are playing a different ballgame.

Comment by jzelinskie 6 hours ago

Lots of very bad uses for JWTs for service-to-service communication, too. There are often way more standard/foolproof alternatives than how lots of people use JWTs on the backend.

I feel like discussions like these usually will surface PASETO/Macaroons/Thin Mints as the fix without acknowledging that the complexity of distributed token passing with arbitrary attenuation isn't a fit for most use cases.

That all being said, sometimes JWT is the right solution for the job! It's the core skill of software engineering to be able to take in all the arguments and tradeoffs and make the right choice for your scenario.

Full disclaimer: Take what I say with a grain of salt because I build a project (SpiceDB) that advocates for a more centralized approach for one of the backend JWT use cases: fine-grained authorization.

Comment by Rapzid 8 hours ago

Yeah, hasn't it been "best practice" for a decade or more to treat JWT like a ticket and swap it for a cookie-based session ID in anything browser-like? Then you just do all the cookie session "best practices" to lock it down.

Comment by jeltz 13 hours ago

I agree with your first part but your edit is a logic fallacy. I don't need to be able to hack something to say that it is insecure.

For example: I don't know how to exploit SAML but I know it is a terrible standard dur to making all of the XML parser an attack surface. I am not a security researcher so I dont know how to find exploits in XML parsers but I know having a huge attack surface is bad.

Comment by jkrejcha 11 hours ago

JOSE can still have problems if it's secure when implemented properly. A lot of API surfaces for them can kinda suck. If secure when held right was equivalent to good, then that would apply also to stuff like X.509

There are better alternatives for a lot of cases, standard session tokens or API keys are a popular one in use in most major websites online and work pretty much perfectly for most use cases.

I'm not gonna say those standards are completely without merit. The best thing about them is that it is some basic standard on passing stuff around that isn't like ASN.1 encoded or whatever, to which the tooling seems incredibly brittle and bug-prone.

Comment by tptacek 12 hours ago

There is in fact a long lineage of vulnerabilities caused by JWTs in real applications.

Comment by unscaled 1 hour ago

[dead]

Comment by locknitpicker 3 hours ago

> Necessary qualifier: for browser-based user sessions.

True. Still the article can't really make any case, other than pointing misuses and throwing a few baseless assertions.

In fact, it surprised me to see such an article featuring so many upvotes.

The primary use for JWTs is to allow resource owners to perform stateless JWT validation,and then be able to trust a JWT's payload to perform authentication and authorization. This doesn't mean what the blogger think it means. These processes become stateless because the resource owner does not need to perform any inline request to be able to tell whether the JWT can be accepted or not. Meaning:

- The JWT is signed and/or encrypted, and the resource owner can use it's JSON Web Key set verify and/or decrypt it.

- the JWT, once deemed valid, includes metadata that helps the resource server determine if the JWT should still be accepted. This includes timestamps of when it was issued at and when it expires, a JWT ID to check a revocation list, and even a few user claims such as user id, audiences, scope, etc. A resource owner does not need to perform any request inline to perform those checks. The revocation list needs to be kept fresh but it can be refreshed as a background task. At most, if the JWT is expected to be single-use, the resource owner is able to run the nonce/JWT ID through a denylist.

- one of the primary values of JWTs is performance. For the vast majority of usecases, the whole verification&validation flow is stateless. This means no outbound request is needed to execute authentication and authorization checks. Instead of plowing through something between 20-100ms of latency to handle auth in each request, the whole flow takes less than 1ms.

I don't think the blogger fully grasps this nuance. Outright asserting that JWTs introduce performance issues completely erodes any trust that the blogger has a solid grasp on the subject.

Comment by dheera 4 hours ago

Why is JWT so messy? base64(something) + "." + base64(something else)?

Why not just base64(JSON.stringify(everything)) ?

Comment by tracker1 14 hours ago

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.

Comment by owaislone 1 hour ago

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.

Comment by hparadiz 13 hours ago

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.

Comment by tracker1 13 hours ago

If your talking about a browser context, where the authority is separate from the requesting body, then expiring even at 30s is excessive for user context, let alone every 1s or every request... you're effectively then inflating every single API request into 2 requests... one for a new token, then another to the API being called. This is irresponsible for not much gain in a user-facing context.

Comment by hparadiz 12 hours ago

You should not be using them for user contexts at all. The cookie should be the session token and the sessions should be stored on the server side where you can simply delete them and the user's login becomes invalid. Using JWTs for this use case is just plain wrong.

Comment by tracker1 10 hours ago

I disagree with you and the article on this... I thought that was pretty clear.

You can use a revocation list with JWT if necessary, and if your JWTs never last more than 15m you'll be fine.. and if your security window is tighter than that, you probably have bigger issues to deal with.

Comment by tasuki 3 hours ago

> You can use a revocation list with JWT if necessary

Yes and let me just add that in many cases the use case is such that a revocation list is not even needed and then JWTs are actually stateless and it's a small win for everyone.

Comment by hparadiz 7 hours ago

I think you can use a JWT just fine to introduce a new user into a system but once authenticated just set a classic session cookie with an expiration. I get when you can't for like an API and sure that's where JWTs are best. But a regular website? Not the biggest fan.

Comment by stavros 2 hours ago

But in that case you're just using sessions, and the JWT is a microoptimization to avoid hitting the DB every request.

Comment by beckler 2 hours ago

The ID token is always a JWT when doing OIDC, and it makes sense in this situation.

Because the ID token is not a set of credentials, but signed information you’d use to create/update a user’s profile.

You can technically use JWTs as access tokens, as the spec doesn’t specify a format for access tokens, but in my experience they’re normally opaque bearer tokens.

Comment by hparadiz 1 hour ago

I prefer saml 2.0 for jit provisioning.

Comment by sieabahlpark 4 hours ago

[dead]

Comment by owaislone 1 hour ago

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.

Comment by littlecranky67 13 hours ago

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.

Comment by vidarh 13 hours ago

The moment you have to look up the user object, you've lost the primary advantage of JWT, and might as well ditch it.

Comment by littlecranky67 12 hours ago

Depends on the system. If you use JWTs for authentication only, they still serve a purpose. Sessions also only serve as authentication, not authorization. Authorization is independent of the both systems, and it depends how you implement that.

There are systems where the authorization is done in the JWT too (i.e. scopes/permissions in the token) - in that case you are right.

Comment by owaislone 1 hour ago

This. Totally defeats the purpose of having JWTs if you have to hit the DB or some service to validate the token every time.

Comment by mekoka 5 hours ago

It depends how you store and look things up. There are so many optimization opportunities and strategies to make the lookups fast that this is pretty much a non-issue in practice (e.g. deny list implemented as any combination of runtime or in-memory index, trie, or bloom filter). At the first invalid bit the lookup fails and the token is allowed to proceed to subsequent auth checks. Which should happen quickly for the vast majority. No need to head straight for the worst possible implementations, like whitelist disk lookups.

Comment by joshmarlow 12 hours ago

It definitely violates DRY but if you keep passing the JWT down the call chain, you can do redundant permission checking in your business layer.

Now the reasonable response to the above is that this should be happening in a dedicated authn/z concern - and that is correct! But when paranoia is called for, it's not unreasonable to have redundant checks in logic where authz is critical.

Comment by jpalomaki 13 hours ago

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.

Comment by littlecranky67 12 hours ago

I agree. The storage space, however, is a different story. Your session DB can grow huge, depending on your session lifetime and your users logout behaviour. Plus, it is a concern in a distributed system (i.e. a token can be validated on every node, vs. a session lookup must be globally in sync)

Comment by 10000truths 10 hours ago

1. For the vast majority of CRUD apps, active sessions will be a very small fraction of the actual storage requirements. A SaaS with 100K MAU may have only 100 or so active users at any given time.

2. Sessions by definition are ephemeral. A database should not be necessary at all, an in-memory cache should suffice.

3. If you really need to distribute session data across multiple nodes, just propagate them asynchronously. Authentication and authorization are semantically idempotent operations. Having to possibly re-auth when making a cross-region request within milliseconds of logging in might be mildly annoying for the user, but consistency isn't a deal breaker here.

Comment by littlecranky67 10 hours ago

> 3. If you really need to distribute session data across multiple nodes

What you mean, "if" - you will need that once you are international. You can't afford to verify every http request against a centralized session store when you have users in Australia, US, Europe, Japan etc. You can't beat the speed of light. My point is, replicating revocation lists that are append-only, only a small megabytes, and can be publicly known, is always easier than syncing session databases for a complexity standpoint.

Comment by matt-p 12 hours ago

Can even put it in redis too, if you have performance issues from looking for it in memory then you have probably have more users than google.

Comment by littlecranky67 12 hours ago

What if you have two servers, one in japan and one in central europe? Where do the sessions live?

With JWTs, you would only need to replicate your revocation list of the last X hours (X being your JWT default lifetime) and probably be in the megabytes for the total list. Easy to replicate that ever 5-10seconds to all your locations.

Comment by what 4 hours ago

You probably don’t need to replicate it? The users hitting your Japan server aren’t going to suddenly hit your central eu server?

Comment by tasuki 3 hours ago

Except when they do...

Comment by mewpmewp2 11 hours ago

If you have multiple services in multiple locations however, you may want to replicate this data, so in this case revocation list as it's much smaller would be far easier to replicate for much less latency overhead.

Comment by what 4 hours ago

Then you have to replicate all the other data too? I don’t see the issue.

Comment by littlecranky67 1 hour ago

That is not automatically given in any system and depends on the system. Authentication, however, is a minimum required feature in most system that would introduce latency. Just imagine sending each and every http request from a user half way round the world to lookup the session - latency would be terrible.

Comment by zsoltkacsandi 13 hours ago

> 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.

Comment by littlecranky67 13 hours ago

Yeah of course, but how does that relate to my point? With JWTs you don'T have a list of valid tokens as state, but only a list of invalid ones (revoked). But the list of revoked tokens in the last X hours (where X is your token lifetime) is always going to be smaller than the list of active sessions given a large enough user base. Hence my original point stands, that the lookup and storage costs are lower than on sessions. Whether or not sessions have session lifetimes does not change the fact at all.

Comment by zsoltkacsandi 12 hours ago

> With JWTs you don'T have a list of valid tokens as state, but only a list of invalid ones (revoked).

Yes, and a lookup operation is a lookup operation.

Your database or data structure used for storing the sessions/JWT revocation entries won't really care whether you look for things that are active or things that are inactive/revoked. If you store it in the right database, both lookups will be O(1), so it is the same (or at least the difference is negligible), regardless of the size.

Comment by littlecranky67 12 hours ago

The story changes if you have a distributed database. replicating a smaller revocation list (that is append only) that will never be more than a couple of MB, is easier to do accross distributed nodes around the world than keeping a larger, session state db replicated. Heck, your revocation list can even be public (it contains only a list of substring of a few bytes of hashes).

Syncing sessins can be done, no question, I would just think JWT+revocation db is easier to implement, yet robust.

Comment by conradludgate 10 hours ago

It can also be encoded as a bloom filter for very fast checks. Then you can defer to the replicated LSMTree that's stored replicated on your local node

Comment by doctorpangloss 12 hours ago

isn't it that you must have a revocation list in many cases? if you cannot get from N to 1 or 1 to 0 states, if you're just going from N to N-1>1, you haven't materially decreased your statefulness

Comment by littlecranky67 12 hours ago

But the revokation list is always going to be orders of magnitude smaller than the list of active sessions.

Comment by doctorpangloss 11 hours ago

that may be, but who cares how big or small it is really

if you are facebook sized, with 1b+ active sessions versus an alternative with 10m+ revocations... the kind of applications that reach this scale, they have enormous amounts of state anyway.

Comment by littlecranky67 11 hours ago

Everybody thinks Mag7 scale, but actually it is more relevant if you are a tiny webservice - but available world wide. If you need to match each and every http request from a user half way around the world against a central db, you can't beat the speed of light. If you can do authentication on each downstream server directly using crypto and JWT validation, you at least save that roundtrip to the session db. The revocation list is tiny (a few megabytes tops), append-only, can be public to the world, and thus easy to replicate to your downstream nodes.

If you are a smaller gig, you won't have to bother with replicating your sessions and keeping them in sync globally.

Comment by ApolloFortyNine 14 hours ago

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.

Comment by tracker1 14 hours ago

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.

Comment by tasuki 3 hours ago

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!

Comment by tptacek 6 hours ago

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.

Comment by jstanley 1 hour ago

How can you provide a stateless logout (that invalidates credentials rather than simply forgetting them client side)?

Comment by tasuki 3 hours ago

So don't do that - and you're stateless!

I can't recall the last time I used a "Logout" button anywhere. I no longer visit internet caffees...

Comment by jotato 14 hours ago

> "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

Comment by hparadiz 13 hours ago

Not checking the signature on every single JWT is the same as storing a password in plain text.

Comment by Natfan 6 hours ago

worse, it's storing identities in an editable format that any attacker can use to impersonate any user, no?

Comment by hparadiz 6 hours ago

Even worse than both of those scenarios. If you don't check the signature anyone can simply write whatever they want in the payload string. The signature is always generated by combining the payload with a private key. Then the receiver uses the public key to verify the signature. If you don't do that the payload can be modified to be anything. Storage not required by the attacker.

It's like prompting for a password but accepting any password as valid.

Comment by andy_ppp 7 hours ago

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.

Comment by mawadev 2 hours ago

Finally, feels like the blog stirs up unnecessary drama

Comment by ChicagoDave 5 hours ago

OAuth2 has been the web security standard for ten years. I don’t recall any major security announcements about it. The OP is confused.

Comment by tomjakubowski 4 hours ago

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.

Comment by jillesvangurp 43 minutes ago

There are a lot of oauth2 adjacent specifications recommended by the IETF and others that are all about using JWTs in the context of standards like oauth2 and openid connect.

You are right that you can do oauth2 without JWTs. But JWTs are a common enough base technology that it is standardized separately under the IETF along with a whole range of related standards. As does the OAuth2 standard and of course a whole range of standards built on top of that and related standards such as OpenID Connect.

The base specification does not mention JWTs, mainly because it predates the JWT RFC by a few years. That's something that's being rectified in the v2.1 draft of the spec which recommends the following:

> It is RECOMMENDED to use asymmetric (public-key based) methods for client authentication such as mTLS [RFC8705] or using signed JWTs ("Private Key JWT") in accordance with [RFC7521], [RFC7523], and their update [I-D.ietf-oauth-rfc7523bis] (defined in [OpenID.Connect] as the client authentication method private_key_jwt).

So, JWT usage is indeed not required, but very strongly related if you look at the broader ecosystem of specifications and implementations. It's hard to ignore JWTs in that context.

Comment by ChicagoDave 2 hours ago

I guess I just assume a good architect would enforce authorization with an identity server and never put anything important on the client.

Comment by Anoian 1 hour ago

[dead]

Comment by rdegges 11 hours ago

=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!

Comment by tored 10 hours ago

Invalid certificate - dark humor.

https://www.paseto.io/

Comment by rdegges 10 hours ago

:o

Comment by philt3r 2 hours ago

[dead]

Comment by blixt 12 hours ago

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)

Comment by Grollicus 14 hours ago

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.

Comment by smetannik 2 hours ago

This debate pops up every few years and the community still seems like don't have a clear consensus

Comment by eranation 8 hours ago

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.

Comment by camgunz 1 hour ago

I think people were using them to skip hitting the database for an auth check on every request, but revocation is hard, e.g. someone quits, but nothing on the backend of their old company's services is checking their JWTs stored in their browser, so they still have all the access they ever did.

Comment by wyc 5 hours ago

What about JWT+DPoP? It would address many of the author's concerns.

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...

Comment by harrall 7 hours ago

JWT is secure.

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.

Comment by himata4113 10 hours ago

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.

Comment by iririririr 10 hours ago

irrevocable* cache

Comment by himata4113 10 hours ago

I've adapted dynamic public-key hotswapping whenever there is a need to revoke tokens as it would simply force all tokens to go to /refresh endpoint instead of the standard 5m cache. Never had to use it though. I've experimented deriving the public key from the uuid so I could broadcast that "keys with this id and this revision should no longer be accepted and should be refreshed", but as I said never ran into a situation where 5 minute expiration wasn't fast enough. That said if you're dealing with critical infrastructure JWEs are the way, you just lose the speed benefits of JWEs as you have to make a request to an internal service to validate and decode, but for everything else JWTs are completely fine.

Comment by encodedrose 7 hours ago

This is an interesting approach especially if you factor in that re-minting a key is usually a lightweight task compared to what most API calls have to interact with.

If the re-minting happens transparently with a user interaction then you spread out some of the request velocity that can come with that (if you're operating at a large enough scale for it to matter for this to be a concern).

Comment by bastawhiz 11 hours ago

One of the linked posts explaining why you shouldn't use JWTs is bizarre at best:

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.

Comment by gabrielsroka 14 hours ago

2019

Comment by 13 hours ago

Comment by darylteo 3 hours ago

This needs to be so much higher.

Comment by 11 hours ago

Comment by miiiiiike 13 hours ago

Security doesn't start or end with JWTs.

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.

Comment by zdragnar 12 hours ago

> look for the JWT access token in a revocation list that is only accessed during sensitive, infrequent, requests

I've clearly spent too much time working with data covered by HIPAA because this sentence gave me a brief bit of panic. The vagueness and extent of what it technically covers means it's far safer to just assume literally everything about your users needs maximum security.

Comment by miiiiiike 10 hours ago

This is the eternal conversation around auth. “The thing you do doesn’t work for the thing I do.” OK. Use something else.

Comment by genghisjahn 9 hours ago

I've used JWTs for a number of server to server interactions and they are amazing for what they are. I've also told many a front end team member to NOT use them.

Comment by BlackFly 11 hours ago

If I understand the point being made here then the idea is that a stateless session via a cryptographically verified bearer token needs a stateful revocation list to eliminate hijacking (a user logout should completely invalidate the login but a bearer token would otherwise continue to be valid) and if you are maintaining state then you can just use a complete stateful session and avoid the complexity of the cryptography.

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.

Comment by bastawhiz 11 hours ago

Revocation lists can simply be replaced with a "tokens not valid before" field per user. When a user logs out, set the field to now(). Reject JWTs that have an iat less than that value. Am I missing something?

Comment by Sohcahtoa82 10 hours ago

What you're missing is that you're still creating state. You're still having to check a database to determine what the "tokens not valid before" value is for that user.

And what if the user is logged in from multiple devices, but only wants to log out from ONE of them? Your solution logs them out from all of them.

The entire point is that it is not possible to have authentication that is both: 1. stateless. 2. secure.

And so if authN is going to be stateful anyways, you might as well just use an opaque token in a database and eliminate all the complexities and foot-guns of JWTs.

Comment by dchest 10 hours ago

Yeah, you made a revocation list but with time value instead of the token value.

Comment by vova_hn2 14 hours ago

One of the articles that TFA links to [0] contains the following paragraphs:

> 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...

Comment by littlecranky67 13 hours ago

>> 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.

You can be sure it is not true, because it is utter BS. JWTs have an "iat" timestamp field (issued at) and in the described case that an attacker has a leaked token, your validation logic simply should refuse any token with iat < $NOW for that identity.

I have JWTs implemented for a site and in my case, users cannot individually revoke tokens - but they have a "Signout from all devices" option. That will basically just set a field "minimum_issued_at" to $NOW in the database for their user, and any tokens will always be validated against the minimum iat timestamp. That is a good compromise in security and simplicity.

Revocation lists have their purpose, though, in systems with heightened security requirements.

Comment by vova_hn2 13 hours ago

> your validation logic simply should refuse any token before $NOW.

Well, this approach throws out a lot of babies with the bathwater. You invalidate tons of legitimate tokens along with the one that you wanted to invalidate and get a thundering herd [0] of clients wishing to re-authenticate.

This is probably not good in case of a really high load.

And if you don't have a really high load, then there is no good reason not to have a stateful session storage.

[0] https://en.wikipedia.org/wiki/Thundering_herd_problem

Comment by littlecranky67 13 hours ago

I edited my comment after I posted it to clearify you do this on a per-identity basis. I.e. every user/identity has a minimum_issued_at field. A user can "sign out from all devices", and that will simply update minimum_issued_at with $NOW.

You are not throwing out a lot of babies with the bathwater if you would do it in a case of a known attack. You would invalidate ALL tokens of a user, which is a sane default especially since usually you wouldn't be able to rule out what other tokens were compromised. And yes, if it later turned out ALL your users and all their token were possibly compromised because you had some kind of security flaw, setting a global minimum_issued_at is exactly what you would do after you fixed the flaw. And yes, that means all your users must reauthenticate.

Comment by vova_hn2 12 hours ago

Thanks for the correction, I didn't think about this approach and it sounds like it should work.

The only comment that I have that if you are already querying users table (or collection in case of NoSQL or whatever), you might as well have a sessions table/collection in the same database/storage and query them together. It seems that difference is not that big.

The purported advantage of stateless sessions is that you can check the auth without querying the main db/storage (maybe only querying a smaller/faster axillary storage).

Comment by littlecranky67 11 hours ago

Think a small (as in client base), but distributed system - i.e. Asia/EU/US locations of a webshop. You can easily replicate/cache your products from a central server, and reuse the cache from the localized ones. But each and every web request would have to be authenticated against a central db somewhere around the world. It is just easier if each node can just validate the JWT themselves by using crypto. All they need to do is maintain a revocation list locally. Now, your revocation list is append-only, can be publicy available and never going to be more than a couple MB. Very easy to replicate/cache this. I can't say the same for a session database.

Comment by megous 11 hours ago

> your validation logic simply should refuse any token with iat < $NOW for that identity.

makes no sense

... ok now it does :) your now is not now, but a stored value

Comment by elcritch 13 hours ago

It really doesn’t seem very hard to have a small invalidation list. Just a redis cache or a simple broadcaster, etc.

Does anyone have an example of how they built a JWT revocation service?

Comment by littlecranky67 13 hours ago

See my sibling comment about the "signout from all devices / iat" pattern. This is only a few lines of code.

If you want to be more fancy and fast, you can use bloom filters to check if a token is in a revocation list.

Comment by feelingsonice 13 hours ago

PASETO is great but there's not enough ecosystem support

Comment by ForHackernews 10 hours ago

https://www.paseto.io/ expired cert on their website that's how popular it is

Comment by 13 hours ago

Comment by lucassz 12 hours ago

> You can't securely have truly stateless authentication without having massive resources, see the cryto.net link above.

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").

Comment by userbinator 4 hours ago

What a bunch of BS...

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.

Comment by cjoelrun 13 hours ago

I ain't never gonna stop!

Comment by InsideOutSanta 13 hours ago

I might stop if somebody linked to an article pointing out an actual problem, rather than making vague and/or incorrect/misleading assertions.

Comment by andreyvit 12 hours ago

Let me bite, as someone who usually hates JWT but sometimes uses it, including for browser auth.

Why JWT is bad: it's a cargo cult solving a non-existent issue in a more complicated way than necessary. An HTTPOnly session cookie containing just a random ID is shorter and easier to handle.

Why JWT is also bad: a typical way to use it exposes too much attack surface. Almost every JWT library has way too much functionality, supports multiple algorithms, and many people are too sloppy with their dependencies, so you probably haven't read every line of code that runs in your auth.

How to use JWT safely:

1. Have a use case that cannot be easier solved with just a random session identifier. For example, one party creates tokens and another unrelated party verifies them. If same party issues and validates tokens, you better have a super high load, unique use case -- but then you're senior enough to not take random advice from strangers.

2. Write your own JWT handling code. It's literally a few lines of code to create tokens and a few dozen to validate. Only implement the exact algorithms and claims you use.

3. In a typical scenario, JWT should still carry something like a user ID which you should immediately verify against a database. Stateless sessions doesn't mean no DB lookups on validation. If you DO authenticate based on the token alone, the token should be super short lived (seconds or single digit minutes).

Comment by hparadiz 13 hours ago

HN is full of people who don't actually fully understand the subject matter speaking confidentiality. And lots of arguing even when they are clearly wrong.

Comment by hparadiz 13 hours ago

JWTs are for authenticating an already trusted system with another system.

Using them as the primary source of truth is an anti-pattern like the blog post is actually saying.

Comment by NetOpWibby 12 hours ago

It's not a blog post, it's a post on Github Gist.

Comment by Aldipower 2 hours ago

Man, once a year the same BS. JWT in an secure HTTP only cookie are perfectly fine, not less secure then a regular a regular session id, but indeed give you the advantage being able to be stateless!

Comment by asattarmd 6 hours ago

JWTs are secure in the sense that they are tamper-proof. And that's all you need for a session token (plus the revocation list). There's nothing inherently insecure about JWTs.

Comment by szmarczak 14 hours ago

No need to stop. The XSS argument also applies when using cookies.

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.

Comment by wccrawford 14 hours ago

I think anything can be abused, and too many people don't have a security-first mindset.

One of the advantages of JWTs is that you don't have to check your database or filesystem to make sure the the user is valid and logged in. All that data is in the JWT. If it's just a static page, it doesn't need to hit any data.

The problem then comes that some developers think that makes it secure, and don't check the database for revocation before doing anything with the account. Especially not for giving out private data. They might check before changing any data.

I think it's a really neat idea that is far too easy to mishandle and create a bad situation. It can save a lot of bandwidth and CPU cycles if you have a lot of non-interactive pages and all you need to know is whether to show that the user is logged in or not. But for actually doing anything, it's practically no better than a session cookie, and it's got a lot of foot-guns.

Comment by jkrejcha 11 hours ago

A lot of times local storage is much less secure than using cookies. Cookies have about 20 years of infra built around it (HttpOnly, SameSite, Secure, etc). There's some weird parts about cookies, but local storage really shouldn't be used for anything security sensitive

Comment by megous 10 hours ago

20 years of security:

sqlite3 cookies.sqlite 'SELECT name, value FROM moz_cookies WHERE isSecure AND isHttpOnly'

And that's a supposedly a master password protected browser. They can't even bother encrypting cookies. Don't be ridiculous.

Comment by szmarczak 10 hours ago

> A lot of times local storage is much less secure than using cookies.

Is it? If an attacker can't do XSS then it's as strong as cookies.

Supply chain attacks aren't an argument here because they can also happen with cookies. CSRF as well. The same can happen in actual executable binaries.

I don't get the 20 yr age argument:

- HttpOnly fights XSS which is impossible to execute with modern frontend frameworks.

- SameSite fights CSRF but the real solution is to disable loading the website in iframes (remember clickjacking?).

- Secure fights MITM which is already fixed by default when using local storage and HSTS is the real deal.

Having said that, I'd say that local storage is more secure than cookies (no need to remember whether you put Secure on or not). Unless you're still using PHP, which means touch grass.

Comment by dariosalvi78 13 hours ago

with cookies you can restrict them to HttpOnly so that they are not exposed to client-side scripts. This reduces the chances of XSS to access the long-lived access tokens (JWT or session ids).

Comment by Sohcahtoa82 10 hours ago

HttpOnly makes it so XSS can't steal your token, but that won't stop XSS from using your token.

Comment by littlecranky67 12 hours ago

This. I store my JWT in a cookie, and the cookie is of course set to HttpOnly,Secure and SameSite=strict. That basically kills XSS. I do not use openid connect, and one of my pet peeves with OIDC is that the access/refresh tokens are always exposed to the JS side (not in a cookie using HttpOnly) in any impl. i've seen.

Comment by adithyassekhar 10 hours ago

> Discord removes it on page load and restores it when the tab is closed.

How does this work? You have no real control over what the browser does when it closes a tab.

Comment by efilife 3 hours ago

I've always wondered how they do it as well.

Second problem - if they remove it from localstorage they still need to hold them somewhere. So are they moved to a simple variable then? It's just as accessible as localStorage. Maybe it's randomized every load?

Comment by mattstir 5 hours ago

Is this at all coherent...? The author really seems to be comparing cookies to JWTs as if they exist in the same category, but it really is apples to oranges here. In fact, one of the first articles they link to spell that out explicitly:

> 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.

Comment by Aeolun 10 hours ago

At this point this just feels like an old man shouting at the clouds.

Comment by dalton_zk 5 hours ago

Stop using jwt, but what we should use when we have a backend and frontend applications in different servers?

Comment by adamddev1 12 hours ago

I remember learning to make sites back around 2019 and seeing so many blog posts and hype around JWTs. It seemed like "this was the way to do it!" But I couldn't understand why session cookies weren't the better, simpler solution. I just used session cookies. Nice to be vindicated in retrospect.

Comment by mekoka 6 hours ago

Every once in a while an article with a sensational title against JWTs pops up in here and I have to wonder if something new was discovered. But nope. It always boils down to the same "can't invalidate it" complaint, which can be addressed with a viable deny list structure (simple in-memory or runtime index, bloom filter, trie). Then there are the vague "it's insecure" claims which when looked at closely, come down to "some people use it wrong".

Comment by green_wheel 6 hours ago

> The JWT specification is specifically designed only for very short-live tokens (~5 minute or less). Sessions need to have longer lifespans than that.

Citation needed. Where does it say this?

Comment by yieldcrv 3 hours ago

(2019)

and the JWT can transport a more encrypted user session as a value

just be intentional

Comment by jongjong 8 hours ago

JWTs are extremely useful for storing basic non-secret authentication info. Session IDs are not as good for most situations and force unnecessary database or memory store lookups. JWT is a more versatile authentication construct because it doesn't prevent you from doing that extra lookup but it also doesn't force you to.

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.

Comment by cyberax 8 hours ago

Stop using JWTs and instead use my copy of JWTs with all the same fundamental issues! But now it also has a custom snowflake serialization format!

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".

Comment by stickfigure 9 hours ago

Like most "always do this" or "never do this" articles, this one is dumb.

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.

Comment by misano 12 hours ago

imo instead of random cookie u can carry some data in it and avoid more joins in database why not ?

Comment by ForHackernews 10 hours ago

I'm not convinced. All the PASETO objections[0] are things that have long since been addressed by every major JOSE implementation.

[0] https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...

Comment by mgaunard 13 hours ago

A whole lot of nonsense from a web guy.

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.

Comment by _el1s7 10 hours ago

I hate these type of clickbait articles

Comment by kobalsky 12 hours ago

I agree that using cookies is better for web sessions but I absolutely despise those using the boogeyman to shoo people away from stuff they don't like, instead of asking them to use their brains.

> 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.

Comment by jongjong 4 hours ago

I've never had any issues with JWT. There's a group of incompetent developers who like to point to a tool as a scapegoat to distract away from their own failure to use the tool correctly.

I think the irony is that the people who make these blanket statements may be idiots themselves and that's why they think everyone else is an idiot. They can't imagine that other people don't have problems using those tools. It's a skill issue.

Some people here built embarrassingly parallel distributed systems with consistent hashing load balancers. JWT is easy by comparison.

To me, it sounds like a child putting their shoes on the wrong feet, getting blisters and concluding that nobody should wear shoes.

Comment by hoppp 13 hours ago

What if I put a jwt in a session cookie?

The post is not descriptive enough

It should explain how to not store JWT instead of just saying JWT is bad.

Comment by waterTanuki 9 hours ago

Everyone has a strong opinion on how to implement auth yet seemingly no one has hard data or peer-reviewed research to back up said opinions.

Comment by EGreg 10 hours ago

Over the past 15 years of developing secure web apps for various organizations, I ended up architecting solutions which had to deal correctly with ITP, webauthn, CHIPS, iframes and more.

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://openclaiming.org

https://github.com/OpenClaiming

It is also used in stuff like https://safebots.github.io/Safecloud/

Comment by scudsworth 7 hours ago

no

Comment by 14 hours ago

Comment by frictasolver 27 minutes ago

[flagged]

Comment by luckilydiscrete 5 hours ago

[dead]

Comment by jheriko 13 hours ago

[dead]

Comment by well_ackshually 14 hours ago

[dead]

Comment by dzonga 16 hours ago

due to the recent FIFA hack - just a reminder - stop using JWTs

Comment by dgrin91 14 hours ago

The Fifa hack had nothing to do with JWTs, it was because FIFA was doing auth on the client side. They would have had the same issue if they used cookie auth.

Comment by mycall 14 hours ago

h4ckernews also accessed an Azure Function App that provided direct download URLs for internal FIFA files, including transfer reports and board level data, due to a lack of RBAC access checks.

Comment by tancop 12 hours ago

if you are fifa please keep using them in the most insecure way possible. release the infantino files

Comment by bijowo1676 10 hours ago

JWTs are in every bit superior to cookies