Stop using JWTs (2019)
55 points by lr0
55 points by lr0
Full title: Stop using JWT to keep user logged in
The author's focus is on not using JWTs for sessions, but the shorter title might still be valid:
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.
I’ve criticised PASETO before (as also mentioned in this nice overview by Thomas Ptacek). With all the versions it has now (and presumably post-quantum ones will be coming), I don’t think it’s improved. It’s better than JWT, but still has design flaws IMO.
JWTs are fine. They're not great. But, we don't need to have strong feelings about them.
https://adriano.fyi/posts/2019-09-10-jwts-are-not-the-enemy/
https://www.howmanydayssinceajwtalgnonevuln.com this is fine
How many seconds has it been since an IP source address was spoofed in a reflection/amplification attack?
We don't say "Stop using IP" because src allows arbitrary values.
I don't think this is the same thing at all. First of all it's not obvious that you can fix this particular issue in IP without significant rollout problems. signing every packet is also a not-insignificant performance degradation. This might be one of the areas where this no truly good solution.
JWT allowing alg=none however is not that. There are a number of obviously better ways to handle this for one thing. 2. You don't have the same rollout issues here. JWT's are not shared infrastructure for the entire internet. Telling someone they shouldn't use jwt is not the same as telling them they shouldn't use IP. Not even remotely.
Thisis utterly unconvincing to me. Show me the exploits or "thats just your opinion man".
?? JWTs are routinely exploited. Someone made one of those one page sites and it was just days since the last "alg: none accepted" bug.
Can JWT be used securely? Sure. But it's easier to just use something that can't be misused.
The alg:none issue specifically should hopefully become less prevalent after my draft finally makes it through the IETF. But it’s been a long and painful journey to get there.
opens my initial access exploits directory
$ rg -i jwt -g '*.go' -l | cut -d '/' -f1 | sort -n -r
cve-2026-29000
cve-2026-21858
cve-2026-21643
cve-2025-68613
cve-2025-20188
cve-2024-28255
cve-2023-29357
cve-2023-28323
It's split between those with actual JWT incorrect uses (CVE-2026-29000, CVE-2025-20188, CVE-2023-29357, CVE-2023-28323) and ones that probably wouldn't have happened if they used traditional database sessions, ie JWT secret extractions from path traversal to JWT forgery (CVE-2026-21858, CVE-2025-68613, CVE-2024-28255). Like carlana said, it's mostly how you hold it, but JWT seems particularly prone to being useful for exploitation in my experience doing lots of reproduction.
For those looking for more discussion on this, there's also What Do We Do About JWT - Security, Cryptography, Whatever Podcast. (The episode on mTLS also touches on some similar topics.)
I’ve been saying this for years, but somewhat influential cargo-culting Spring developers keep on using JWTs, leading to more cargo-culting. sigh.
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. ... "stateless" authentication simply is not feasible in a secure way.
I think the reason people end up with JWT, is that most web apps are Single Page Applications, where you deliberately shift lots of complexity away from servers and to javascript VMs. Cookies come with their own set of problems, like how dev-environment will struggle with cookie domain localhost or needing to set up individual sessions with each backend (if you have multiple). Rightly or wrongly an OAuth resulting in a JWT bearer token, appears simpler at first glance.
most web apps are Single Page Applications
Choosing a bad “solution” because you already made a different bad choice is the first lesson most web focussed developers live through the problem is not enough learn from it, most just seem to keep repeating the same bad decisions.
I mean, to be fair, most of the time that decision is not available to developers, SPAs are typically a fait accompli today.
it gets missed a lot, but oauth bearer tokens aren't required to be JWT; their format is intentionally unspecified. Authelia, for example, makes them nonces that reference sessions stored server-side, by default, for all the reasons this piece describes. (it does offer optional JWT support, but strongly discourages it)
Cookies come with their own set of problems, like how dev-environment will struggle with cookie domain localhost
Can you elaborate on that? I've never had any problems with setting cookies on localhost in my web servers.
TBH, I haven't worked with cookies in a while, so my experience may be out-of-date. I found this article that says you can store cookies on localhost as long as you omit the domain.
(warning, that article looks AI generated to me)
When I was looking for a way to implement session tokens for a personal project of mine, this is the gist I ran into. I originally persisted, and tried to use JWTs, but... quickly reached a point where it felt like I wasn't sure I was using them properly. So I had a look at PASETO, found a suitable Rust library, and that was a lot simpler, and much easier to understand.
When it's a lot simpler to use a thing properly, when it has less footguns, I'm sold.
I didn‘t understand why JWTs are a problem. I recently implemented an app following https://lucia-auth.com/ for auth. This was my first time implementing auth (my goal was to learn). It recommends a short lived statelss JWT access token together with a long lived refresh token, which is verified by the database. Both are secure HTTP-only cookies. Could someone point to source which explains this for beginners?
I didn‘t understand why JWTs are a problem.
I wrote something about it six years ago, and as far as I know most of that is still relevant.
move auth-server to subdomain and keep long-lived refresh token there (http-only is good), while your main app would only have access to the stateless access token (and in a lot of cases you don't need it to be http-only, as you might need to read it from JS)
I use sub paths for my frontend (Svelte) and backend (Python). I didn‘t yet need to read the access token in my frontend.