Stop using JSON Web Tokens for user sessions
ds-security.com
ds-security.com
> For security reasons, it is advisable for users to log out from a web application once they have completed their tasks
No, the application should be resistant to XSS instead. Online banking and such are automatically logging out to prevent someone stepping away from the device and another person abusing the logged in session.
> Frequently, when a Logout function is present in the application and is implemented with JSON Web Tokens, the application stores the JWT in an insecure location, such as the JavaScript code itself or the local storage in the user’s browser
This claim is as valid as "Frequently, when a Logout function is present in the application and is implemented without JSON Web Tokens, the application stores the plaintext password in an insecure location". The storage location is completely independent of whether it's a JWT or not.
Or we can admit that vulnerabilities are a likely possibility, despite all of our efforts. Therefore the most secure approach is to understand that limiting the impact of one vulnerability is a reasonable way of dealing with it.
Otherwise you're suggesting running application code as root on the machine isn't a problem, since your application has no vulnerability.
The application should be as resistant to xss as possible but things do sneak through and we should try to limit the damage in other layers.
An example is that you could think you have no xss issues because you use react to do your rendering. Meanwhile you have a window.location = something_from_url which is just as capable of running js code if you’re not careful.
Having the auth (whatever it is) in a http only cookie is one protection. Having it time limited is another. For some applications locking it to an ip address might make sense.
It’s not an either / or thing.
> JSON Web tokens (JWTs) for session handling instead of cookies.
JWT and cookies aren't mutually exclusive, you can put a jwt in a cookie, unless it's too big. There are two issues: Where do you store a sessions key, and how you create the session key. The author correctly argues that the session key should be stored in a cookie, but doesn't make any arguments against JWTs, as far as I can tell.
> This approach is insecure because essential flags like HTTPOnly are not supported.
This wording is odd, because it's the other way around? It's not that HTTPOnly isn't supported when storing the session key in js, but that HTTPOnly enforces that you don't do it in js.
> the logout function often merely overwrites the JWT on the client side, leaving user sessions valid until timeout.
That's obviously bad, but there are also people who just set a different cookie, without making the previous session cookie invalid.
Then the author goes on to explain a possible XSS attack, but fails to mention that in the example XSS would also be very bad without the vulnerable session key. The injected js could do something evil right away, without steeling the session key. (But yes, it's worse with the session key exposed)
The author doesn't mention the relatively common flow of using refresh tokens together with short lived session keys.
Overall, I think this article can be misleading. A confused beginner could read this article and misinterpret it to mean that protocols like OAuth or OpenID Connect are insecure, just because they use JWTs in some places.
JWTs have a lot of issues, but they do work without localstorage/XSS being involved.
I hate to word it this way, but the problem is that JWTs are misused.
The reason I hate to word it this way because the natural step after “X is misused” is “X is prone to misuse”. Which is usually right. But the reason why JWTs are prone to misuse is partly because the underlying problem is hard (security, authentication, the web) and partly because some of the libraries which work with JWTs provide application developers some footguns.
Go ahead and use JWTs if you want, but spend some time understanding the underlying problem space. Authenticate the token before parsing it. Choose one specific signing algorithm—don’t allow tokens to be signed with any algorithm your JWT library supports.
And yes—simple session IDs, rather than JWTs, are often a good choice. But JWTs are fine too. The obvious reason to use JWTs is to speed up authentication in your front-end somewhat—if your front-end can authenticate a request by validating the JWT, and maybe checking it against a list of revoked tokens, then that means you can start processing the rest of the request without waiting for a network request for authentication.
> but spend some time understanding the underlying problem space.
This is the most important point, but you're other points are also very true. And less one-sided. :)
The point of JWT vs opaque tokens is that you can just inspect the token itself to derive permissions without hitting any sessions in DB, right?
This means we need a short-lived access token (5 min or so?) so that sessions are revoked in a reasonable time if the token is stolen somehow (this is already scary to me TBH, someone can still do horrific things in 5 mins of intrusion time, but that's another story). Now my question is... how do you even handle the refresh token then?
I understand that long-lived refresh tokens means you can actually go to the DB/microservice/whatever and check if the refresh token has been revoked (via log out for example) since they'll valid for much larger intervals, so you can afford the session lookup... But if a refresh token is long-lived, what is the difference from having an actual long-lived access token? You'll still be handing out access token for a while. I can't see the difference here.
What am I missing?
EDIT: I could see the point if the threat models for both were different (e.g. having access tokens in memory, refresh tokens in a super safe vault, like e.g. ChatGPT would need to do for actions) but having both refresh+access tokens in the same threat model seems like it does nothing to me.
If you've just got one service endpoint, it doesn't buy you very much. Just mint a session token and be done with it.
The design philosophy seems to be, use short-lived security credential A (the jwt) for all of your requests, which (again, _theoretically_) risks its exposure, and have a Security Credential B that you keep very secure, and use that to create new instances of Security Credential A.
If SC-A were a cookie and SC-B were kept in a hardware store, then I think this might be logical, but where they're both basically cookies, it seems kind of crazy to me, too. Maybe there are some details in how the actual storage of the cookies varies, but I think it's Hopium all the way down.
By contrast, refresh tokens go to the auth server that can do whatever checks are necessary to make sure the user is still allowed to use the service. This would typically incur DB lookups and require more complex auth logic than simply validating "yeah, this JWT is legit and still in-date".
If the user is expected to use a programming language and hit the API many times a second, then even a short expiration and moderate refresh really help (might fully authenticate every 5 mins, which could be thousands of requests for some APIs).
Not guaranteed for the lifetime of refresh tokens, which is the case for access tokens.
> whats the difference?
A 5 min access token is valid for 5 minutes. A 5 minute refresh token can be revoked before its even used.
1. You can check for user changes when the refresh token is used and a new access token is requested (user deleted, permissions changed, access revoked)
2. Refreshes can go to a different endpoint, whereas access tokens could be used with services that don’t even have access to user auth services.
Edit: There is something to be said for ditching the refresh token and just re-authenticating when the access token expires. The benefit is that you don’t need to store the username/password in the client code to re-auth, and you might avoid some relatively expensive password checking on the server, but that is probably pretty minor.
You're getting refresh token using credentials (e.g. login/password). Refresh token is long-lived and basically allows you to use service without typing login/password every 5 minutes. How much refresh token should live depends on your security requirements to how long user can user your service without typing his password.
You're getting access token using refresh token. This allows server to check whether you've been banned or something like that. If you encode access permissions into access token, it also allows to adjust those permissions. So your access token lifetime is a balance between performance and security.
If your API already uses some kind of service key, then refresh token does not make much sense (may be it does to provide uniform implementation for web clients and service clients, but not from security perspective).
My opinion is that all those things are more complicated than necessary. You could use the same token both for access and refresh purposes (if token is outdated, check it using database and return new token, then client will use new token for subsequent requests).
No. Focus on “token” instead of “JWT.” JWT is just a standard.
As I understand it, de-centralized verification isn't a necessary characteristic of a JWT. There are token constructions that make that a priority, however[0].
Those downstream services can then perform all their authorisation checks using just the JWTs alone. They don’t need to reach out to the authentication service to validate them, or lookup permissions, that’s already happened upstream. If those downstream services need to call other services, they can just pass along the JWT, and it allows the next service to validate the request is performing an allowed operation, without having to contact the auth service, or perform user lookups etc to determine what’s a valid operation. It’s all encoded in the passed along JWT.
There’s also security advantages because the issuing and primary validation of JWTs can be isolated to a single well vetted service, that has very limited connectivity, and every other service uses JWT validation with public keys to perform permission checking, rather than endless user auth lookups. Those service would also have to contact your central auth service to get new JWTs issued, ensuring there’s only a single place in your infrastructure that holds the sensitive private signing keys, and has the ability to issue new tokens. Which means you have a single gatekeeper that determines what types of tokens other services can access, allowing you to enforce limits on those services, even if they don’t cooperate (maybe they’re owned by a different team). It also provides a central location to limiting the blast radius of compromises, you can mass invalidate tokens with cooperation from other services, and stop issuing new tokens to services that might be compromised.
Ultimately JWTs shouldn’t be used for “efficiency” or “performance”, but rather as a tool that allows you segregate sensitive authentication and authorisation infrastructure from all your other services, and prevent “normal” services from adding ad-hoc authentication and authorisation capabilities in way that’s hard to audit or control, without limiting their ability to enforce authorisation requirements.
JWT is a format for tokens, it's not a choice between JWT and opaque tokens, as JWT can be opaque and transparent tokens can be in another format.
JWT just happens to be a format with good support in javascript. As a format, it's quite unremarkable, but as a standard it's a complete piece of shit where footguns abound. Anyway, it's easy to work with, while the alternatives tend to have the opposite features, being incredibly hard to work with, but easier to use correctly then wrongly.
Transparent tokens (the ones everybody knows how to verify) have the advantage over opaque tokens that you pointed out (they are easy to distribute). On the other hand, they are hard to revoke, so people tend to use short-lived ones. And yes, it's common for an application to have a long-lived opaque token that they only use in an authentication service to get short-lived transparent ones (that they use everywhere).
Anyway, the article is about the infrastructure for supporting JWT in javascript.
Oldish but still largely relevant: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
I can't make it past the first sentence:
> ...utilize JSON Web tokens (JWTs) for session handling instead of cookies.
One has nothing to do with the other. You can use a jwt and cookies. You can use a db session and use a jwt and not use cookies. Etc etc
I read that as JWTs in a cookie as a replacement for session cookies with data stored on a server. There are nice things about that, but with a fail safe logout mechanism via server side revocation lists JWTs are not that stateless anymore.
EDIT: I do agree that the article misses the point a bit, but I also agree with the article that sessions cookies are wonderful and you almost always should use that instead. It all depends on your environment. One thing I can say: never use Active Directory as a token server the amount of glue you need is insane.
JWTs are stateless.
JWTs reduce the distributed system complexity of having every microservice talk to an auth system in the flow of every request. But with that, there are tradeoffs.
You cryptographically sign JWTs, then you don't have to check them against a session/authc/authz system. JWTs come with an expiry, and your systems statelessly trust them until they expire.
A problem is that a user can't revoke a JWT by logging out or changing their password (unless you build extra infra to store the invalidations and fail closed), so if someone steals the JWT, they can continue to redeem it until expiry.
This is what the article is trying to warn against.
Bluntly, this.
If you need need this functionality, for example, when you log out of a banking app... and if you've implemented token revocation, eg. by storing a list of 'revoked' tokens in a database somewhere when someone 'logs out' or gets banned.
...then, in most cases you should not be using JWT.
Once you make your JWT stateful, by storing it in a database there is no reason to use JWT.
...and since that is the only way to implement that functionality using JWT, there are many times when JWT is not a suitable choice.
If eventual consistency is good enough, presumably you could use something that looks like a bloom filter of revoked tokens and only check ones that match against the revocation service, right?
JWTs are a cryptographically sound means of conveying identity between systems, assuming the implementation doesn't allow `algorithm: none`. I'm genuinely curious why you perceive JWTs as useless when they're stateful. It's a common opinion, so I assume there's something to it. My best guess is that it stems from JWTs being "sold" as a stateless means of conveying identity. In which case, I think they fall short of that promise. I just don't equate that with useless. I'm using the word useless, which I interpreted your statement "no reason to use JWT" to mean, but feel free to correct me on that.
Applications should both invalidate JWTs on logout and validate JWTs haven't been revoked for every request. Yes, it's performance overhead and increases infrastructure demands.
The problem is that people want JWTs to be both secure and fast. But the reality is they can only have one of these qualities at a time. They can sacrifice a bit of speed and get security, or sacrifice a bit of security for speed.
Far from trivial to implement, but it gives you some amount of performance and security at the same time.
What I am trying to say here is that JWTs used in single page applications are dangerous because you have no layer of protection against XSS attacks.
Who says you _have_ to? You could set a jwt as an httponly cookie and use it to exclusively validate API requests in your backend.
The answer here is just to store your session JWTs in HttpOnly secure cookies, something we’ve done for years.
It honesty simplifies everything anyway. The client has no need to see it or manipulate it. The client code just watches to see if its requests come back as unauthorized and boops you into the authorization process if you are. No need to manually append any sort of auth onto any request. The cookie jar does it for you automatically. It’s really how you should be doing it already.
Only real hitch is you can’t cross domain barriers, but that’s not insurmountable. Couple ways to handle it, we generally prefer a little handshake process and a separate JWT per domain. That said, we try to keep everything on subdomains and set the cookie for .example.com
I appreciate interesting solutions that bend the rules of the underlying technology, but (1) the web needs to be treated as if it is what it actually is, and (2) papering over the parts that cause pain is not helping anything.
And set CORS correctly (probably by not having anything in it), and deal with CSRF, and make sure you don't have any XSS issue.
It's definitively not insurmountable. But the XSS problem the article focus on still applies. (You shouldn't have any problem with XSS anyway, it's a solved problem that most of the times shouldn't even require thinking about it to keep it solved.)
Make sure your app is actually using a JWT framework, not a lesser version, and implements basic security practices.
Upon access token refresh and login, refresh token is also rotated.
Refresh token expiry is tens of days, access token some hours.
Their opinion is that this is enough security.
IMO refresh token is vulnerable being stored in localStorage, and relying on users logging in and/or triggering token refresh to rotate refresh token is not really that great.
It's not a zero-sum situation. There are risks and vulnerabilities in every approach, that's why we as web devs take advantage of multiple safeguards to ensure safety when sensitive information is to be transmitted over the wire.
As sibling comment says, this is what SameSite is for.
If it's a POST form, SameSite=Lax or SameSite=Strict won't send the cookie.
If it's a GET form, SameSite=Strict won't send the cookie. SameSite=Lax might, I'm not entirely sure.
localStorage is totally accessible to any JavaScript running on the page, so your session can be completely hijacked and used later.
Not completely true - the attacker can not exfiltrate the token but they can still make malicious requests right there in the victim's browser via XSS.
If you can inject javascript, it's game over anyway.
> HTTPOnly cookies are safe from XSS attacks.
No, you can still do pretty much anything that cookie enables you to do. You just can't get the actual cookie string.
Yeah, but as you pointed out the one thing you can't do is get the cookie. Having the auth token yourself as the attacker is a way different story then just having XSS vulnerabilities. You can still "do" a lot, but you still have to get another user with the token you want to interact with the page with your XSS to "do" what you want.
You need to do this in both cases.
However, with cookies, you have to be careful with the SameSite flag and also be mindful that some older browsers may not support it and so it can be tricky to fully restrict where the cookie will be sent in all scenarios.
Works flawlessly now. If you use an external identity provider, you can hypothetically avoid storing any cookies at all in first party terms. All you'd have would be 3rd party AAD tokens or whatever.
The only reason we even need first party client state is because we want to allow each user simultaneous app sessions that have lifetime decoupled from IdP semantics. This is what we store in the URL query (a guid). Sessions are still bound to user principals, so you would get yelled at if you tried to screenjack someone else's.
Also, IIRC a parent frame can retrieve a child frame's current URL no matter what.
The only added 'protection' of the session ID with cookie approach is that it forces the attacker to perform the attack in-situ inside the user's browser... But that's the best place for the attacker to perform the attack from anyway so it adds no value at all. It wouldn't make sense for the attacker to steal a JWT and then use it to make a request from their personal machine using their own IP address...
Note that httpOnly flag when using a cookie also doesn't add much value for the same reason. The hacker doesn't need to know what the session ID is in order to be able to fully compromise an account and do whatever they want with it.
The main drawback of JWT is that you can't reliably revoke a token after it has been issued. The solution for that is a combination of:
1. Set a short expiry date.
2. Have some kind of 'isDisabled' (or similar) field on the account (or keep a blacklist of account IDs) which allows you to instantly disable a compromised account so it doesn't matter whether or not they have a valid JWT token.
Although the second point means you will need to perform a DB lookup of the account (which some would suggest defeats the purpose of JWT), it's a lot simpler to lookup an account record than having to keep track of a session object and remember to clean it up when the user logs out (while also handling all possible server failure/restart scenarios which would otherwise leave behind stale sessions); managing sessions on the back end can be especially challenging in multi-process and multi-host applications... This is where JWT shines.
https://stackoverflow.com/questions/27067251/where-to-store-...
The Stack Overflow question is nearly a decade old. This is hardly new. This is not a JWT issue.
Yes, things like Keycloak and such follow _bad practice_. Still not the fault of JWT.
It's strange that the article doesn't discuss at all where the JWT is stored in that case. It's one thing if it's stored in local storage (I would avoid that) and a completely different thing if it's stored in-memory so that potentially malicious scripts don't have access to that location.
This leads to a multitude if problems, but who cares?
haha, what?!
this is not true.
Is this a clueless manager thing?
Go figure.
Edit: yeah sure downvote me into oblivion. I'm not throwing away perfectly functional equipment because it doesn't support the latest and greatest ciphersuite. I'm also not planning on a being a roadblock on everything, it's balancing act.
You can still print from a phone or present from a laptop, just not with solutions relying on insecure services.
It requires some effort is all.
It is just fine.
If anyone is allowed to inject javascript in your site, samesite=lax httponly cookies aren't gonna help much either.. the attacker can fetch /api/me/delete with credentials: include (just like you would have to do in your js code) and it's game over.
And extensions can access httpOnly cookies as well. Not to mention other applications can simply access the plain sqlite db.
So really, just use local storage for tokens.
This way, if you want to expire sessions via refresh tokens, you can simply layer it on top of this, by setting a refresh token in a cookie and adding some extra logic to the api calls.
I mean how to do it properly?
I guess in this case, you have a security company advocating against JWT... I guess it could mean that JWT is bad for their business because their business relies on applications to be insecure in order to sell the solution?
Or is it some kind of conspiracy against single-sign-on?
I had thought the main way JWTs are stored and transmitted in a web context is via Cookie/Set-Cookie?
> This attack would not be possible with a cookie based session mechanism where attributes like HttpOnly would prevents injected JavaScript Code from accessing the JWTs.
You can put the JWT inside the HttpOnly cookie, right? What am I missing?
People using JS frontend frameworks to handle every single feature of the frontend, so that they have to let the cookie unprotected or store the token into a JS-only storage.
If you really need to use JWT-s then store the refresh (just normal UUID looking token that is validated on the backend) token in a httpOnly cookie and JWT in local/session storage, use 10-15 minutes expiration and you are somewhat OK on logout= (the XSS is still maybe exploitable). On logout make sure to invalidate the refresh token.
In my opinion a better way is to just use a good old encrypted/signed/httpOnly/sameSite UUID=123 cookie, convert that to a JWT in your APIGW/BFF when talking to backends.
I would not try to cram JWT-s into cookies they are too big, but maybe these days nobody cares about the extra bytes
Why does the length matter compared to when they are sent with cookies or with a special header?
Or many usually have separate domain/subdomain names for API and static content in the first place.
I think having a separate prefix/subdomain would be generally good practice for defining scope which should be authed as well.
Isn't that a net reduction in security?
Users are more vulnerable to phishing attacks if you've taught them to constantly login and logout of the applications they use.
Much better to have them sign in infrequently but more securely using 2FA.
Basically nothing to do with jwts. More to do with xss
And FYI it is not even a law.
I actually don't know any use case for Strict, but it makes sense, so it's probably useful.
And None is for when you want to explicitly allow CSRF (what is useful some times).
And either way, it's best to always set that flag on sensitive cookies (not only authentication, but anything that leaks user information too), even if it's the documented default, because browsers make quite a mess of their default.
Your job is to solve problems. In the real world. For real people. Start doing it.
In the following Stackoverflow thread (https://stackoverflow.com/a/60941643) I described a way to store a JWT in Cookies while keeping convenient to use payload from the Javascript stack (for instance to display the user name).
This is achieved by splitting the JWT in 3 parts (header, payload and signature) and storing it into 3 different Cookies which have different properties. The _header_ and _payload_ would be accessible from the web application while the _signature_ is configured with HttpOnly and therefore unaccessible from the web app. The inconvenient of this method is that you have to reconstruct/concat the 3 parts server side.
Disclaimer: it's actually an experiment which has for purpose to get the better of both world and it has not been tested from security standpoint.
Hum... HTTP has an entire authentication header.
There is also an entire set of standard practices to authenticate by cookies.
The only thing there isn't a standard is for handling authentication data with random code. As that's a pretty stupid thing to do.