This doesn't only apply to the way JWT tokens are usually used for sessions (no persistence). The default session store for Devise (Rails) and .NET Identity is cookies, on the client. They are encrypted with a secret key and decrypted for authentication. Identity in particular allows you to store any number of "claims" in the cookie, such as a username or role. Because the cookies are signed and HTTP only, this is safe from attackers, but this method, along with pretty much any method that isn't storing some sort of state on the server, has the same 3 problems listed in the article.
1. Logout doesn’t really log you out!
2. Blocking users doesn’t immediately block them.
3. Could have stale data
I know there are ways around this with a really fast refresh time, or as I've heard, storing some sort of signature in the cookie, but I personally prefer a plain old server-side session store with something like Redis, or even just an in-memory HashMap. Authentication doesn't have to be that complicated.
1. Logging out deletes the cookie, you are really logged out. If your session cookie got stolen, you have other issues but I don't think this is really a matter of being 'logged out'. It is pretty easy to implement 'revoke all sessions for this user' type of logic with Devise and Devise does this of the box when a user changes their password.
2. Permissions are orthagonal to Devise. Devise stores the user ID in the session and loads the user model on every request, any permissions / blocking system would chain from there.
3. I can't think of anything that devise stores in the session where staleness would matter, other than things intended to be checked for staleness, like the salt that is used for the aforementioned revoke all sessions on password change functionality.
For 2 and 3 I was mainly referring to Identity, although I'm not sure how it works internally. For 1, I think the main issue is that when someone logs out, or you log someone out, you aren't guaranteed that they are actually logged out. There are cases where this does matter.
How does Devise handle 'revoke all sessions for this user'?
This is exactly the point of the blog. If you are using JWT + Workarounds to make it secure, then you'll loose all the benefits. This is why virtually no one in fin-tech use JWT.
In general, I agree that sessions should be opaque tokens stored in an http-only, strict same-site policy cookie. I just had a few problems with a couple of the arguments:
> 3. Could have stale data
The only use case I've seen for storing authorization information in a JWT is for something like OAuth2 scopes, which is different than strict authorization rules. They're more like delegate rules, but you should only treat those as a first line of defense before you do the checks that the authorizing user actually has access to.
Also, it's just as easy to let a redis cache go stale. Seen it more than once with this same security issue.
> 4. JWT’s are often not encrypted so anyone able to perform a man-in-the-middle attack and sniff the JWT now has your authentication credentials. This is made easier because the MITM attack only needs to be completed on the connection between the server and the client.
If someone can MITM your connection in plaintext, they have your credentials, whether or not you use a JWT. Yes, any information you encode in a JWT is plaintext, so if you put personal information in there, consider it leaked. Am I missing the argument here?
Nothing is "secure or not" - technologies/mitigations are secure against particular attacks. HTTPS is generally secure against passive network eavesdropping, but does nothing to stop local file inclusion in a web app.
Just because there are attacks or ways around a particular defense doesn't mean it's worthless, that's why we have defense in depth.
I disagree with the main thesis for why JWT is a problem. JWT isn't necessarily encouraging you not to hit the DB for user lookup. This is the claim the article makes as a problem with revocation.
It reads like a really long thoughtful article based entirely on false assumptions for how to best use it.
It's ok to carry around some encrypted state in your tokens for some uses cases.
Geez, even RSA is not secure if you push this thing hard enough. Cryptography is all about making it difficult to decrypt, not impossible to decrypt. One can easily setup a conspiracy theory that the government can record all the traffic, and decrypt the dump in the future when computers are fast enough.
Exactly. We don’t even know for sure that there isn’t a faster way to factor numbers with classical computers.
If you’re handling sensitive data, you have to understand the tools you’re using and what their relative weaknesses are. Best practices are nice. But it’s far too easy to make a mistake following them if you don’t understand the principles behind them.
“X is always secure” is a great way to ensure X is not implemented correctly.
It shouldn’t be, either. Things evolve around it, definitions change, but also things need to be less secure because of convenience or necessity. An emergency stop button that halts machinery, causes damage to the machines, stops production, but saves lives, can be the target of any number of imaginative scenarios of malicious actors hitting the button to cause damage or be a part of some grand hacker scheme. But we definitely don’t want to secure that until it takes badging in and a 2FA prompt just to use it.
I respectfully disagree with both of you. Security should be binary, within a given set of requirements / implementation parameters and the intended threat model. Security must be binary within the space of “are you authenticated or not” (within the massive context specific web of trust and private keys) is binary and if it weren’t that would be a problem.
It seems like quite a silly position. By the author's own admission, it is possible to mitigate all the risks warned about. I certainly don't agree that something is never OK to use because if implemented improperly it wouldn't be secure. If I took that seriously, random password generation is insecure, because someone could just use an cryptographically insecure PRNG.
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.
> 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 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…
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.
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.
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.
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.
I actually find this post/ad for Redis pretty ironic, because Redis is actually a really great solution for storing revoked tokens (since you can just store the token with an expireat equal to the token's expiry timestamp). I do think it's a serious issue that most jwt howtos don't mention expiring tokens and/or refresh tokens, but "JWTs are not safe" is hilariously hyperbolic.
1. Logging out deletes the cookie, you are really logged out. If your session cookie got stolen, you have other issues but I don't think this is really a matter of being 'logged out'. It is pretty easy to implement 'revoke all sessions for this user' type of logic with Devise and Devise does this of the box when a user changes their password.
2. Permissions are orthagonal to Devise. Devise stores the user ID in the session and loads the user model on every request, any permissions / blocking system would chain from there.
3. I can't think of anything that devise stores in the session where staleness would matter, other than things intended to be checked for staleness, like the salt that is used for the aforementioned revoke all sessions on password change functionality.
How does Devise handle 'revoke all sessions for this user'?
> 3. Could have stale data
The only use case I've seen for storing authorization information in a JWT is for something like OAuth2 scopes, which is different than strict authorization rules. They're more like delegate rules, but you should only treat those as a first line of defense before you do the checks that the authorizing user actually has access to.
Also, it's just as easy to let a redis cache go stale. Seen it more than once with this same security issue.
> 4. JWT’s are often not encrypted so anyone able to perform a man-in-the-middle attack and sniff the JWT now has your authentication credentials. This is made easier because the MITM attack only needs to be completed on the connection between the server and the client.
If someone can MITM your connection in plaintext, they have your credentials, whether or not you use a JWT. Yes, any information you encode in a JWT is plaintext, so if you put personal information in there, consider it leaked. Am I missing the argument here?
This is just a straight marketing post for Redis.
I suspect the engineers that helped write this article are either very strongly for redis or cringed knowing their content would be used this way.
No system is 100% secure.
Actual title on the website.
Edit: To watch clickbait work in real time, check OP's submit history.
From my experience, that's not the case for almost anything. In fact, I'd consider it a dangerous position.
Just because there are attacks or ways around a particular defense doesn't mean it's worthless, that's why we have defense in depth.
I disagree with the main thesis for why JWT is a problem. JWT isn't necessarily encouraging you not to hit the DB for user lookup. This is the claim the article makes as a problem with revocation.
It reads like a really long thoughtful article based entirely on false assumptions for how to best use it.
It's ok to carry around some encrypted state in your tokens for some uses cases.
If you’re handling sensitive data, you have to understand the tools you’re using and what their relative weaknesses are. Best practices are nice. But it’s far too easy to make a mistake following them if you don’t understand the principles behind them.
“X is always secure” is a great way to ensure X is not implemented correctly.
Why not just use cookies and be done with it? This looks like an advertisement for Redis Enterprise.
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.
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.
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.
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…
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.
I kinda agree it looks like an ad for redis, since it doesn't even considers alternatives.
[0] - https://hasura.io/blog/best-practices-of-using-jwt-with-grap...
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.
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.
(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.
Deleted Comment
Dead Comment