Remix.run Logo
solatic 8 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

jzelinskie 15 minutes ago | parent | next [-]

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.

RagingCactus 7 hours ago | parent | prev | next [-]

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

nine_k 7 hours ago | parent | next [-]

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

agwa 7 hours ago | parent | next [-]

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.

mewpmewp2 5 hours ago | parent | next [-]

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.

hparadiz 7 hours ago | parent | prev | next [-]

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.

agwa 6 hours ago | parent [-]

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

hparadiz 6 hours ago | parent [-]

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.

agwa 6 hours ago | parent [-]

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

hparadiz 5 hours ago | parent [-]

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

stickfigure 2 hours ago | parent | prev [-]

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

saganus 4 hours ago | parent | prev [-]

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!

jiveturkey 3 hours ago | parent [-]

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

saganus 2 hours ago | parent [-]

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.

0x696C6961 3 hours ago | parent | prev | next [-]

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.

robertlagrant 6 hours ago | parent | prev | next [-]

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

agwa 6 hours ago | parent [-]

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

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

y2244 3 hours ago | parent [-]

Wow lol

chuckadams 5 hours ago | parent | prev | next [-]

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

LgWoodenBadger 6 hours ago | parent | prev | next [-]

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

hparadiz 7 hours ago | parent | prev | next [-]

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.

figassis 4 hours ago | parent | prev [-]

Stateless JWT revocation: https://blog.nellcorp.com/new-aproach-to-jwt-revocation/

dchest 4 hours ago | parent | next [-]

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?

figassis 3 hours ago | parent [-]

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

catlifeonmars 34 minutes ago | parent | prev | next [-]

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?

RagingCactus 4 hours ago | parent | prev | next [-]

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

figassis 3 hours ago | parent [-]

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.

throwaway7783 4 hours ago | parent | prev [-]

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

kyrra 8 hours ago | parent | prev | next [-]

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.

tptacek 6 hours ago | parent [-]

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.

jeswin 2 hours ago | parent | next [-]

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

tptacek an hour ago | parent [-]

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

ForHackernews 4 hours ago | parent | prev [-]

Spec writers and library authors are human? Who knew

tptacek 4 hours ago | parent [-]

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.

ForHackernews 4 hours ago | parent [-]

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.

andai 3 hours ago | parent | next [-]

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

tptacek 4 hours ago | parent | prev [-]

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.

ForHackernews 4 hours ago | parent [-]

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/

tptacek 3 hours ago | parent [-]

I don't recommend PASETO either.

doc_ick 2 hours ago | parent [-]

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

kasey_junk an hour ago | parent | next [-]

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

tptacek an hour ago | parent | prev [-]

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

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

Rapzid 2 hours ago | parent | prev | next [-]

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.

jkrejcha 5 hours ago | parent | prev | next [-]

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.

jeltz 7 hours ago | parent | prev | next [-]

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.

tptacek 6 hours ago | parent | prev [-]

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