back

by rvz·6y ago·view on hn ↗
> Q: If I tried to use this token after 10 years, would it still work?

> A: Yes, it will be valid forever unless the server specifies an expiration time when generating it.

The implications of this is beyond calamitous. This is why Disney+ and Fortnite logins were hijacked in the first place. I can harvest all these JWT logins with a good chance of people forgetting to ever logout. Hence this, with one push of a button I can abuse an API endpoint to mass delete everybody or what not. Best part about JWTs? They are unencrypted by default.

For that reason, Just do not use JWTs.

8 comments
> This is why Disney+ and Fortnite logins were hijacked in the first place

Citation? I'm in total agreement with the problem of unexpiring tokens, but last I heard the suspected culprit in Disney+ was credential re-use. If there's newer info, I'd love to get a pointer to it.

> For that reason, Just do not use JWTs.

That seems a bit sweeping. You can apply the "unencrypted by default" dilemma to just about everything. Noting that you have to take an extra step doesn't invalidate the entire concept.

JWT's generally are not used without setting the expiration date, just like you wouldn't have a server-side session continue forever as long as the user presents their session cookie.
I think the problem is that you need to either make the JWT have a short expiration or engineer a way to invalidate them before the expiration if the need to do arises.

From what I can tell most people implement a blacklist of invalid tokens that need to be expired early. This isn't exactly the same as a traditional sessions table since it turns the problem on its head. You're likely to have a lower number of early expired JWTs than valid sessions.

The other way you might be able to solve this is to generate a per-user JWT key and use that to sign the JWT or embed that in the JWT. When you need to force a log out, just regenerate that value for that user.

In our app the JWTs have a 5m lifetime. When a JWT is generated we also generate a single-use refresh token.

When a client tries to use an expired JWT the request fails, and the client will then exchange the refresh token for a new JWT/refresh token pair, and finally retry the request with the new token.

The refresh operation can reject the request if the user has been deactivated (it's basically a new login request, using the expired JWT as the username and the single-use token as the password).

Perhaps I'm misunderstanding, but how does this help in the case of a compromised token? Doesn't this assume the attacker hasn't also compromised the refresh token?

Presumably, the token and refresh token are both stored in the client-side app. If that gets compromised, the attacker now has the username/password combo they need to restore the session after the T+5mins has expired.

Since the refresh-token is single-use, the user will be logged out when trying to refresh their own token, and will presumably then login again, which should invalidate the attacker's refresh token.

But I agree it's not a perfect system. This is meant to specifically address the problems of long-lived tokens, since JWTs are hard to revoke without checking a blacklist on the server-side.

The main problem is that localStorage is more vulnerable to some classes of attacks than secure, http-only cookies.

Imho this is a useless safety 'feature'. It only saves you from a mitm-attack and even then you hope that the mitm did not see your refresh token.

The moment you store data with Javascript it will be visible for any Javascript.

> I think the problem is that you need to either make the JWT have a short expiration or engineer a way to invalidate them before the expiration if the need to do arises.

That's not a problem. It's literally how they were designed to be used: expiration time and nonces.

So because someone gives bad advice or has a bad understanding on a technology, you should not use it? Seems a little dramatic, don't you think?

JWTs, like most things, work perfectly fine if implemented properly and you understand what you're doing.

Alternatively you could just use a token that has sane defaults, rather than having a tool that could bite you or could bite your organization after you depart.
I've implemented JWT's in apps more times than I'd like to think about, and never has it been done without ensuring the tokens are signed, signing keys rotated regularly, tokens having a short lifecycle, etc. and none of these required some deep expert knowledge just a couple of hours of research on best practices.

Again, it's not about the tool, it's how you use it.

This is the Irrevocability Myth from "Capability Myths Demolished" [0], a classic article explaining why JWT-style fusion of authentication and authorization is not unsafe.

[0] http://srl.cs.jhu.edu/pubs/SRL2003-02.pdf

> Best part about JWTs?

Just to be clear, JWT do support expiration time and nonces.

For implementations to be vulnerable, they need to intentionally avoid standard security practices.

The tool isn't the one to blame.

So, not only do you have to ensure that your use of JWTs is sane, you have to ensure that the library you're using is doing sane things (by default, or by configuration)?

The right thing to do should be the easy thing to do. There's little "easy" about the "proper" use of JWT.

> So, not only do you have to ensure that your use of JWTs is sane

You're the one using JWT. You can't blame the tech if you screw up how you apply it.

> you have to ensure that the library you're using is doing sane things

Isn't this concern observed in 100% of cases where a third-party lib is used?

> The right thing to do should be the easy thing to do. There's little "easy" about the "proper" use of JWT.

That is not true at all. It's quite simple: either you know what you are doing or you develop broken software with security problems. If you decide to adopt a security technique and then fail to learn the very basics then the tech is not to blame for the problems you create for yourself.

Or just understand the technology you are using and the use case for it. PS.: You clearly did not understand JWTs.
What else should we use that is not over-engineered?
Honestly, I find that PASETO [0] is a better candidate to replace JWTs since it supports encrypting these tokens and doesn't have multiple ciphers to choose from, unlike JWTs. They can be used just like them for signing, etc just without the weak cryptography in the token.

Fernet [1] was close to being an alternative but I've seen that even Branca [2] tokens look much more like a saner alternative for replacing JWTs.

[0] - https://paseto.io

[1] - https://github.com/fernet/spec

[2] - https://branca.io

For sessions? A random token and a key-value store.
Cookies have been battle tested and are very secure. Enable http only mode and you can’t even access them from JS thus eliminate possibility of take over / token theft.
You can store & pass JWTs in cookies, provided they're not huge.
But the server must send them.
Where else are JWTs coming from? You can't pass the signing key to the client, that defeats the point.
It has to come as cookie from the server if you don't want to save it with Javascript.
Is there a problem with that? So long as your CORS headers aren't screwed up for your use case, passing JWTs around via cookie headers should be fine, right?
The server can invalidate the token at any time.
To do that you need to store some information about the token centrally so that any server receiving the token can check to see if it is valid or not - and if you are going to do that why not use an opaque random token and store all of the data that would have been in the JWT in the store?
Not the OP, but I guess the TL;DR for me is that it's usually ok for sessions to be loosely consistent, and you can upgrade to "hard" consistency where you need to.

Enforced session state consistency creates a hard runtime dependency on the backend service that provides that data and that service needs to scale to handle every single client request with low latency because the token to session state lookup dictates a floor on the response time of every other service you can possibly provide.

It's not "wrong", of course lots of systems work like this, it has some advantages!

It's just a tight runtime coupling that constrains how you can scale in some ways.

The way I think about it is, if you have jwts you can always add additional "hard consistent" session state in the way you describe, but you're not forced to use it for everything.

You have options, you can make tradeoffs. And in engineering, room to make tradeoffs is a great thing to have.

As an example, with basic jwt sessions you can write say, a non-sensitive geodata lookup service for frontend (don't really care if user session is "valid" for an extra minute after "logout") in a lambda say, scale it to bejesus, but still be able to attribute requests to a user and do rate-limiting, and it has no availability dependencies on anything else.

Ok sure though, maybe change profile info (a low volume request), you need to eat a hard invalidation lookup.

Storing a dozen bytes of 5,000 expired token ids until they expire vs several hundred bytes of 100,000 sessions.

Granted, its use case leans towards big systems, or systems that cross boundaries between orgs.

Could you encrypt the JWT content with user-specific keys and cycle those keys in case of an issue?