back

by colesantiago·5y ago·view on hn ↗
OK, but what's the real alternative here? I'm sick of these JWT hate articles on HN all the time with no universal solution.

Why not just use cookies and be done with it? This looks like an advertisement for Redis Enterprise.

10 comments
You could have short-lived JWT tokens so it wouldn’t matter because they only last 5-10 minutes or less. They could be refreshed or if using OAuth, simply redirect an unauthenticated user and they’ll be reissued based on the cookies stored by the OAuth provider… assuming the redirect doesn’t break your app of course.

Alternatives? First, you could live with the possibility that tokens are valid for some period of time after logout because it usually doesn’t matter - generally you delete the cookie and the user is logged out, even if technically they could restore the cookie later. They won’t, unless you’re under attack.

Other alternatives: You could use Redis for session data as pointed out with the random key in a cookie and delete the session when done to invalidate the cookie.

You could also use Redis to keep token IDs that you want to invalidate or block. You could use something like Open Policy Agent to distribute a list of invalidated tokens to each server.

Finally, you could send your JWTs to a centralized authentication service — single point of failure, yes, but you could record invalidated tokens to memory and responses are very quick and easy to audit. With careful planning you could reduce the risks in having a single central service to validate issued tokens.

I’m sure there are other ways to mitigate this risk. The general reason why folks don’t recommend JWTs is because it’s too easy to make mistakes in the validation logic. But the same is true (with different possible mistakes) when you roll your own session cookies. At a certain point I think you have to either assume competence or you have to suggest that developers use identity proxies in the cloud, or frameworks others have written, and never implement this themselves.

But yes, this is a rather transparent advertisement for Redis as a KV session store.

> You could have short-lived JWT tokens so it wouldn’t matter because they only last 5-10 minutes or less.

The author argues that you can't revoke a JWT - suggesting that 30min is the usual default. To their point, if 30min is too long, then 10min is probably still too long.

However, implying that 30min is too long for a token to remain stale suggests that you aren't really working at the scale that JWT was destined to address (Facebook, Twitter, etc.). If you really are the unfortunate soul trying to solve per-request authz, specifically at the scale that JWT was designed for, then I feel deeply sorry for you. For the 99%, i.e. the rest of us, 30min is just fine.

> You could use Redis for session data as pointed out with the random key in a cookie and delete the session when done to invalidate the cookie.

I'm not picking at your argument, honestly, I'm just pointing out the absurdity of authz without JWT (or similar) at scale. Redis is eventually consistent: what happens during a network partition?

Saying that the JWT expiry is a vulnerability is tilting at windmills.

> The general reason why folks don’t recommend JWTs is because it’s too easy to make mistakes in the validation logic.

Bravo, that's the real problem with JWT right there: it's too easy to misuse - especially when convenience or demanding customers enter the picture. It also has brain-dead specification opportunities like signature-free tokens.

Could you expand on why 30min session validity after explicit logout is okay? I don’t mean to sound accusatory; I would like to understand your reasoning.
If you have an attacker that can obtain the token within 30min, it is reasonable to assume they might obtain the token immediately, and use it immediately too.

JWT expiration protects against situations where the token is stored (or made to be stored) somewhere improper and later used, not being pilfered during proper use.

As the article argues, it doesn't even protect against a malicious user using stale credentials to wreak havoc, such as a disgruntled employee that had access to the precious admin panel being fired.

If the JWT is saved as a cookie, you can delete the cookie and the user’s browser is safely logged out. The threat model is that a user or third-party could intercept and reuse the JWT after logout. Sure. But then a malicious actor could re-use a JWT before you logout from the app also, which is a much larger risk. Malicious browser extensions for example could hide that they’re making clicks or taking actions in tabs just as they hide ads from you. Don’t get me wrong, extensions are sandboxed, but… any sandbox can be broken. In the end, whether or not your JWT was revoked at logout doesn’t affect the risk of malicious activity all that much as long as cookies behave the way they should. And as long as your JWT has appropriate expiry timestamps.
I absolutely agree. That’s why I said:

> you could live with the possibility that tokens are valid for some period of time after logout because it usually doesn’t matter - generally you delete the cookie and the user is logged out, even if technically they could restore the cookie later. They won’t, unless you’re under attack.

> The general reason why folks don’t recommend JWTs is because it’s too easy to make mistakes in the validation logic.

That isn't why I don't recommend JWTs, and it's not why this article is not recommending JWTs.

> Logout doesn’t really log you out!

> Blocking users doesn’t immediately block them.

> Could have stale data

^ JWTs fundamentally are not compatible with server-side authentication revocation.

Your typical developer is like, "Well, I don't care much, I'll use JWTs anyway", but then some poor soul has to deal with the ticket titled "Blocked user still able to access service", or "User still able to access service after logout", or "session never expires!" (oo... look, you configured it incorrectly).

I've been on the other side of having to deal with screwed up JWT implementations other folk have built.

It's not fun.

...and yes yes, there are work arounds, you can have rotating short lived tokens and long lived refresh tokens and a database of revoked tokens...but you've just reimplemented server side session authentication yourself, and it's probably wrong.

> I think you have to either assume competence...

I think that's terrible advice. You should never assume developers are competent to implement authentication. It's a Hard Problem, like, writing a database, and frankly, only specialists are competent to do this correctly. Anyone can writing some kind of data store, but it's a bit harder having it ACID and concurrently multiuser.

> or you have to suggest that developers use identity proxies...

...but yes, this is probably the only useful piece of advice to give to people: If you use a 3rd party authentication provider, you can delegate responsibility for doing the hard work of making sure they have a solution for the hard things; at which point, you don't really care if its JWT or cookies, or whatever.

I still wouldn't recommend people use JWTs.

"database of revoked tokens...but you've just reimplemented server side session authentication"

There's one important difference between session authentication and JWT+revocation: the revocation list can be distributed asynchronously, whereas a session list needs at least read-after-write consistency.

It's a minor point for most use cases, but occasionally can be very significant.

That said, the primary problem with distributed authentication and distributed revocation is either it slows down your processing as you wait for confirmation of new entries on the list, or you have a risk that actions might be allowed when another part of the system has tried to block them. We generally think of distributing authorization data asynchronously in the positive: adding a new user permission, rolling the list out to every authz server. But when you’re trying to block access immediately, it can be difficult.

Not to be negative—if your JWT expires after 15 minutes and your list updates every 10 seconds, you’ve improved your reaction time quite a bit for revocations.

Also, you can have an eventually consistent session store if you don’t mind that sometimes users might see a 403 they shouldn’t, if your app can cover it up such as by requesting content from a different region or waiting a bit. Similar to the idea of using a load balancer to pin sessions to particular servers.

I suppose if you really want to avoid the hop to session storage as much as possible, you could have your applications servers continuously refresh a lightweight Bloom filter or other probabilistic data structure storing maybe-invalidated sessions in memory, and in case of a potential hit, confirm it with the session store. Then your logout endpoint just needs to wait until all servers receive the data to tell you you're logged out (I suppose CAP applies here). If this sounds like overkill to anyone reading this, then it probably is!
I do want to follow up - there are two parts of JWT issuing that are problems and why I prefer using JWTs only issued by OAuth providers (third-parties that know what they’re doing) - 1. JWT tokens you issue yourself might not have expiry dates in them - I am assuming that your JWT tokens are valid for a reasonable duration such as 2 hours or less, otherwise you should probably use a different technology - and 2. Like SSL, you will have to rotate the credentials used to sign the JWT, otherwise anyone could pretend to be anyone else, at any time.

So it’s not that JWTs don’t have risks but the risks are overstated. It would be like saying never use Redis because by default it doesn’t have secure SSL or a proper password system and thus anyone could access it. Security isn’t easy, but it can be done, and often looks like a series of mitigations, monitoring tools and trade offs… such as how long sessions last, or planning for features like key rotation or session revocation in advance…

Don’t be so sure about the third-party auth providers. Even Auth0 accidentally allowed token validation bypass by specifying a weird capitalization of “none” for the algorithm. https://insomniasec.com/blog/auth0-jwt-validation-bypass
Aren’t JWT tokens only supposed to be good for 10-15 minutes? I know using flask-jwt you have to go out of your way to make them last longer than that and it isn’t recommended.
Yes but a lot of times people don't do that properly, some frameworks have incorrect defaults or people don't want to deal with writing the logic to handle refreshing tokens after that 10-15 minute window etc etc.

They are insecure but it's not a problem if you follow best practices like those, the difficulty is a lot of people don't because they don't really understand what it is they're doing or potentially causing by making the changes that let them be lazy or do things the easy way.

You could probably change the title to articles like this from "JWT Tokens are NOT safe" to "JWT Tokens are NOT safe when you ignore all security practices and take the easy way out" but that doesn't make for a flashy title.

I know some folks who thought they could use JWTs from a traditional LDAP identity vendor (open source Active Directory so you can manage computer login credentials centrally, issue Kerberos tokens, etc.) but they managed to misconfigure their JWTs by following the defaults from the vendor so the JWTs issued were valid forever. When I tried to explain that they couldn’t be revoked without key rotation, I got some very blank looks. I’m not even sure the tokens had an iat (issued at timestamp) though if they did you could use that to at least ignore tokens older than a certain date. They assumed because they deleted the cookie at logout, it wouldn’t matter that the session could be re-used forever. Then they implemented session limits by setting a cookie expiry time. /face-palm

Issuing your own JWTs also means you have to keep the secret or certificate used to sign them secure or if it leaks anyone could impersonate anyone else. This is especially problematic if admin or role assignment is embedded in the JWT.

Expiry is just one of the fields you can set. Also if you’re curious about what’s inside a JWT, the next time you see one from an OAuth cookie perhaps, copy and paste it into a tool like https://JWT.ms run by Microsoft and it can show you the details. Commonly you want to load a JWKS (with multiple keys possible for easy revocation) to validate the signature, and check the iss (issuer of the JWT), aud (your app registration or audience of the token) and expiry date of the token. Oh and don’t forget to hard-code or reference the JWKS validation method so you don’t accidentally allow “none”. JWTs are not without their complexity, but the same could be said about maintaining secure session cookies and implementing 2FA yourself perhaps.
There's "JWT hate" articles on HN because people keep implementing JWT auth without understanding it - they want an easy, cheap way to do auth when talking to different services, but it can be a huge security flaw since you can't do revocation. There's a lot of ways to configure JWT, so it's very easy to shoot yourself in the foot.

JWT for short-lived tokens is fine - it can work well for signing requests between microservices. If you want to give them to end users, use refresh tokens.

As with anything, the alternative depends on your needs and use case. There is no universal solution.

+1
Refresh tokens are the real alternative, IMO.

I kinda agree it looks like an ad for redis, since it doesn't even considers alternatives.

Hasura [0] has a great article on how to make front end authentication as secure as possible.

[0] - https://hasura.io/blog/best-practices-of-using-jwt-with-grap...

Agreed. Long(er) lived refresh tokens, and then having signed access tokens such as JWTs so that the API server doesn't have to hit the database on every request.
No solution is universal. That said: use sessions. They’ve worked fine for decades and there’s a 99% chance they work fine for your app.
Definitely use cookies. Opaque tokens. Works great.

All the good versions involve cookies somewhere - they’re the most resistant to all the various forms of attackers on the web.

Many use cookies in order to stamp short lived tokens, though.

There’s no reason to put all the user info in redis and this article makes no valid argument for it. Give the token a unique ID and store that ID in redis (or some other fast store). Validate the token by checking its sig and verifying that its ID is in redis. Store all the signed metadata you want on the client, revoke it by removing its ID from redis. Addresses all cases where the token is stale. So basically redis becomes your token whitelist, not your store of user metadata. Problem solved.

Signed blobs of data such as certificates and web tokens are a very powerful massively distributed cache where the entity that benefits from the data being cached is also the entity responsible for persisting it. This is a wonderful optimization whose sole drawback is that you need an external way to decide when that entry is invalid. Solve that problem, don’t abandon the entire concept of the distributed cache.

It is funny that this is the way most web auth systems worked back before the JWT brand was invented. I made one that had a prefix extension bug, but you didn’t see the marketing drumbeat against it until it got a name and you could paint a target on its back.

(E.g. branding something is often a transition from people using language as a club against hunger, wild animals and the unknown to using it as a club against the other man.)

It is not unusual for systems like this to have some details such as verifying the cookie against the db if somebody is doing a critical operation and not being too worried about a five minute window for people reading articles and such.

High volume attacks need their own countermeasures.

Simple session tokens are fine, always have been.
TLS client certs?