> 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.
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.
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.
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.
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?
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.
> 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.
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 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.
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.
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.
For API access, there's no point using JWTs either. In the vast majority of cases, a randomly-generated API key is sufficient and JWT would be unnecessary complexity (you're using TLS, right...?) and in the odd high-security usecase where the key still must not ever be sent to the server, you can use eg. client certificates.
And if for some reason you have a weird freak edgecase where you really do need a cryptographically signed stateless token... JWT is unsafe[1] and you want PASETO[2] instead.
Edit: To be clear, with "API" I'm assuming you mean "third-party API", in the original sense of the word. If you just mean "a bunch of endpoints for my SPA", then you should use none of these things, and just use sessions. It's not really an "API" in the "API key" sense.
I just haven't updated my blog for quite some time, due to a combination of circumstances. I'll probably start blogging again at some point in the near future.
So some of those things are true, but you can definitely and almost certainly should expire tokens (and reissue before expiry new tokens) after a short time window, which alleviates some of the security concerns mentioned.
Some useful parts of JWTs is they can contain information that can act as a cache so that you don't have to go to the server to show certain parts of your UI or things like names and so on.
It's worth evaluating them for your use case rather than assuming they are 100% bad because of the reasons outlined in the article.
> So some of those things are true, but you can definitely and almost certainly should expire tokens (and reissue before expiry new tokens) after a short time window, which alleviates some of the security concerns mentioned.
That doesn't really help, because now you need some other sort of token to authenticate the user for handing out a new short-lived token, and that new token now has the same inability-to-revoke problem. You've just moved the problem.
> Some useful parts of JWTs is they can contain information that can act as a cache so that you don't have to go to the server to show certain parts of your UI or things like names and so on.
You don't need a JWT for this. You can just store that in localStorage directly. JWT is not a storage mechanism, it's more of an encoding format.
> That doesn't really help, because now you need some other sort of token to authenticate the user for handing out a new short-lived token, and that new token now has the same inability-to-revoke problem. You've just moved the problem.
No you don't, as stated you can refresh your token before it expires.
> You don't need a JWT for this. You can just store that in localStorage directly. JWT is not a storage mechanism, it's more of an encoding format.
I think you can validate the authenticity of your JWT without having to go to a database. This is much more secure than local storage or even a plain old cookie.
JWT provides a useful set of features and just because in your opinion it isn't useful plenty of people disagree with your points.
A server can issue a JWT and forget about it. Most cookie auth strategies requires the server to persist the token to confirm validity. Also, a JWT can work across a microservice architecture easily for the same reasons.
The biggest problem with JWT is there is no revocation mechanism which IMHO is a non-starter. Introducing revocation mechanisms generally means introducing central state and now you're back to why not just use a session token? Not to mention the numerous issues JWT has had over the years.
The correct comparison is JWT vs sessions, not JWT vs cookies. Cookies are a storage mechanism, and you would compare them to localStorage. Assuming you meant JWT vs sessions, yes, there aren't any except that it's super trendy.
They let you answer the question "did some other machine I trust find this entity to be a valid user, having this identity and the rights they assert they have?" without asking that machine directly or storing anything about the user. More or less. Provided the keys haven't leaked, of course.
The chief difficulty this introduces is manually invalidating tokens. You end up having to store some data and/or ask some other system questions about users (blacklist-related, in either case) after all, reducing the initial benefit. But distributed sessions will run into similar problems anyway.
Basically if your auth lives the same place as all parts of your application the user will ever see, don't bother with it because the benefit is little to none, otherwise, JWT's a solid maybe.
Thanks for the insight! If anyone cares to provide any clue about best practices and how to handle token storage, you are more than welcomed to provide any insight.
I've been looking at this for months without getting a clear, noncontroversial answer.
Even with this documentation, it is still unclear what to do if you have a SPA on another host than your backend (so you can't use cookies), and you do not want to use server sessions.
Using `oidc-client` from the frontend could work, but that bundle size[0] is absolutely insane.
Yes, but when the server does not send the token as cookie the only option is to store it with JavaScript. And yes, that also means (any) JavaScript can access the token.
From my perspective, it is a session cookie + some other verifiable attributes. For instance if you are using OAuth you can include the OAuth client ID in the JWT. Now you don't need to look it up each time.
Cookies have a special storage place in the browser and limited storage space. This limitation makes it so that usual practice for cookies is to store only a UUID-style identifier client-side and the rest of the data for the session server-side, necessitating a database round-trip before you can even know who's hitting your server.
JWT allows you to use the browser as the fatter client that it has been slowly growing into. Because JWT is JSON, it can store anything, and you're only limited by how large you want to make your web requests. (they're sent with each request)
At the end of the day, they're both ways to build state into a stateless protocol.
JWT is just some arbitrary JSON data, with a few customary fields, cryptographically signed by whichever system issued it. You send it to the client, which may be a browser or mobile app or whatever, and they send it with each request to any related systems. A system receiving a JWT can verify that the stuff in it was certified as true or accurate by the issuing system, given only the correct keys to check the signature and no further info about the user. One might, for example, put user identity info & user roles (for permissions) in a JWT.
This can be useful for e.g. checking login status at proxy or cache servers without having them connect to a database or ask other systems (so, works very fast), for microservice bullshit, or for systems with lots of access points and unified login (think Google, with Youtube, Gmail, Docs, and so on—I don't think they use them, but that's the sort of situation in which one might)
The main downside is that you can do stupid crap with them like not set an expiration, and you can't really expire tokens prematurely (say, on user role changes or on user deletion) without re-introducing some centralization. Neither are crippling but they do make it less the magic bullet than one might hope.
I've seen lots of tutorials for single page apps/api architectures that specifically store JWT in localstorage, it's endemic.
What I haven't seen though, is guidance on how to deploy SPA web apps using cookies properly (with either jwt or session token in http only cookies)
From what I have found, there are only a few options:
1. host api at 'api.mydomain.com' and frontend from 'mydomain.com'. You will have to deal with CORS and options requests which can add significant latency for non-simple CORS requests. Not to mention extra configuration with CORS headers.
2. Serve assets/frontend from the API (ie, rails static assets, Django collect static, etc). Downside is that you have to deploy everything together.
3. Reverse proxy so that api/frontend are on the same domain, and you can use cookies without CORS. Api lives at 'mydomain.com/api' and frontend at 'mydomain.com`
I am currently doing 3, which if you are using AWS, you can use CloudFront as the reverse proxy by using a separate dristriburion for your api and your Frontend and using behaviors to route traffic by path. In fact, that is what AWS recommends: https://aws.amazon.com/blogs/networking-and-content-delivery...
So for 3, for example:
You have CloudFront -> Loadbalancer -> EC2 for your api with a path of /api in CloudFront, you can set CloudFront to never cache. You don't pay for bandwidth between EC2 -> CloudFront, you only pay for bandwidth out of CloudFront:
>Outbound data transfer charges from AWS services to CloudFront is $0/GB. The cost coming out of CloudFront is typically half a cent less per GB than data transfer for the same tier and Region
For your frontend, you can host in s3 -> CloudFront with a catch all path for non-api paths (allows client side push state to work in SPA).
CloudFront in this case also is where SSL termination happens. You can also to full end to end ssl if you enable that in the CloudFront and your API can also terminate ssl at the LoadBalancer.
The other benefits to this approach is that you can usually just use the default auth systems for your web apps. You can also create a CNAME for your API loadbalancer to be api.mydomain.com and use that for non-web clients. In the case of Django Rest Framework, there is both cookie and token auth enabled by default.
One thing missing: the "cryptographic signature" isn't required to be a cryptographic signature by the standard. You need to ensure, by configuration or by library defaults, that the algorithm of 'none' is not allowed, otherwise someone could freely modify and replay the JWT for all time.
70 comments
[ 2.9 ms ] story [ 143 ms ] thread> 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.
Granted, its use case leans towards big systems, or systems that cross boundaries between orgs.
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.
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
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.
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).
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.
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.
The moment you store data with Javascript it will be visible for any Javascript.
That's not a problem. It's literally how they were designed to be used: expiration time and nonces.
JWTs, like most things, work perfectly fine if implemented properly and you understand what you're doing.
Again, it's not about the tool, it's how you use it.
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.
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.
The right thing to do should be the easy thing to do. There's little "easy" about the "proper" use of JWT.
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.
[0] http://srl.cs.jhu.edu/pubs/SRL2003-02.pdf
And if for some reason you have a weird freak edgecase where you really do need a cryptographically signed stateless token... JWT is unsafe[1] and you want PASETO[2] instead.
[1] https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
[2] https://paseto.io/
Edit: To be clear, with "API" I'm assuming you mean "third-party API", in the original sense of the word. If you just mean "a bunch of endpoints for my SPA", then you should use none of these things, and just use sessions. It's not really an "API" in the "API key" sense.
Joepie91 once did a small nodejs project for me, that's why I recognized your link.
I'm kinda worried as "not an acquaintance" about his post about depression and that he didn't post anything since 2016.
Anyone know what happened?
His GitHub still seems to be active (https://github.com/joepie91).
I just haven't updated my blog for quite some time, due to a combination of circumstances. I'll probably start blogging again at some point in the near future.
Some useful parts of JWTs is they can contain information that can act as a cache so that you don't have to go to the server to show certain parts of your UI or things like names and so on.
It's worth evaluating them for your use case rather than assuming they are 100% bad because of the reasons outlined in the article.
That doesn't really help, because now you need some other sort of token to authenticate the user for handing out a new short-lived token, and that new token now has the same inability-to-revoke problem. You've just moved the problem.
> Some useful parts of JWTs is they can contain information that can act as a cache so that you don't have to go to the server to show certain parts of your UI or things like names and so on.
You don't need a JWT for this. You can just store that in localStorage directly. JWT is not a storage mechanism, it's more of an encoding format.
No you don't, as stated you can refresh your token before it expires.
> You don't need a JWT for this. You can just store that in localStorage directly. JWT is not a storage mechanism, it's more of an encoding format.
I think you can validate the authenticity of your JWT without having to go to a database. This is much more secure than local storage or even a plain old cookie.
JWT provides a useful set of features and just because in your opinion it isn't useful plenty of people disagree with your points.
I do see use cases for JWT for machine-to-machine auth though.
The chief difficulty this introduces is manually invalidating tokens. You end up having to store some data and/or ask some other system questions about users (blacklist-related, in either case) after all, reducing the initial benefit. But distributed sessions will run into similar problems anyway.
Basically if your auth lives the same place as all parts of your application the user will ever see, don't bother with it because the benefit is little to none, otherwise, JWT's a solid maybe.
> A: Typically, you would store it in the localStorage, after the user logs in and gets the token.
This seems to contradict Auth0's guidance to not store tokens in localStorage since it's vulnerable to XSS: https://auth0.com/docs/security/store-tokens#don-t-store-tok...
Gives various suggestions depending on your use case.
[0]: https://bundlephobia.com/result?p=oidc-client@1.9.1
I suppose I'll never learn.
(looks hopefully at the HN crowd)
JWT allows you to use the browser as the fatter client that it has been slowly growing into. Because JWT is JSON, it can store anything, and you're only limited by how large you want to make your web requests. (they're sent with each request)
At the end of the day, they're both ways to build state into a stateless protocol.
This can be useful for e.g. checking login status at proxy or cache servers without having them connect to a database or ask other systems (so, works very fast), for microservice bullshit, or for systems with lots of access points and unified login (think Google, with Youtube, Gmail, Docs, and so on—I don't think they use them, but that's the sort of situation in which one might)
The main downside is that you can do stupid crap with them like not set an expiration, and you can't really expire tokens prematurely (say, on user role changes or on user deletion) without re-introducing some centralization. Neither are crippling but they do make it less the magic bullet than one might hope.
What I haven't seen though, is guidance on how to deploy SPA web apps using cookies properly (with either jwt or session token in http only cookies)
From what I have found, there are only a few options:
1. host api at 'api.mydomain.com' and frontend from 'mydomain.com'. You will have to deal with CORS and options requests which can add significant latency for non-simple CORS requests. Not to mention extra configuration with CORS headers.
2. Serve assets/frontend from the API (ie, rails static assets, Django collect static, etc). Downside is that you have to deploy everything together.
3. Reverse proxy so that api/frontend are on the same domain, and you can use cookies without CORS. Api lives at 'mydomain.com/api' and frontend at 'mydomain.com`
I am currently doing 3, which if you are using AWS, you can use CloudFront as the reverse proxy by using a separate dristriburion for your api and your Frontend and using behaviors to route traffic by path. In fact, that is what AWS recommends: https://aws.amazon.com/blogs/networking-and-content-delivery...
So for 3, for example:
You have CloudFront -> Loadbalancer -> EC2 for your api with a path of /api in CloudFront, you can set CloudFront to never cache. You don't pay for bandwidth between EC2 -> CloudFront, you only pay for bandwidth out of CloudFront:
>Outbound data transfer charges from AWS services to CloudFront is $0/GB. The cost coming out of CloudFront is typically half a cent less per GB than data transfer for the same tier and Region
For your frontend, you can host in s3 -> CloudFront with a catch all path for non-api paths (allows client side push state to work in SPA).
CloudFront in this case also is where SSL termination happens. You can also to full end to end ssl if you enable that in the CloudFront and your API can also terminate ssl at the LoadBalancer.
The other benefits to this approach is that you can usually just use the default auth systems for your web apps. You can also create a CNAME for your API loadbalancer to be api.mydomain.com and use that for non-web clients. In the case of Django Rest Framework, there is both cookie and token auth enabled by default.
https://tools.ietf.org/html/rfc7519#section-6.1