Stop using JSON Web Tokens for user sessions
ds-security.comThis post is about XSS, not JWTs...
> 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.
>No, the application should be resistant to XSS instead
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.
I don’t understand the downvotes here.
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.
Using xss one might target login form and steal username/password instead of a token. So I do not see argument here against jwt. Sure the xss will have to be more sofisticated(?)
I’m not arguing for / against any specific technology. I’m saying that relying on a lack of security flaws in one layer isn’t a great idea.
The general idea of the blog post is correct, but so many things are worded in slightly incorrect ways. I think this blog post needs a few edits.
> 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.
Agreed.
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.
Yes!
> 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 title should have been “Stop using localstorage for user sessions”.
JWTs have a lot of issues, but they do work without localstorage/XSS being involved.
> Stop using localstorage for user sessions suggest an alternative approach
As a side question (or rather, what I expected the central point of the article to be instead): how are you supposed to actually use JWT?
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.
In a distributed system with a dedicated authentication service, you can have that authentication service validate every JWT at your infrastructures entry point, and then only allow requests with valid JWTs to go downstream.
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.
You could use the access token for each request. The advantage is that it is a simpler approach, and does away with the 5-minutes restriction you refer to, as the logout/invalidation would be immediate and not in 0-X minutes where X is the access token life in minutes. The disadvantage is that serving each request will involve making a round-trip with the auth service. This means at the minimum a DB read for every request, but could also mean a call to a separate, (possibly a third-party) auth microservice, and with possible fraud-detection measures each request. Depending on your use-case, you can drastically reduce the number of calls made to the auth server/database by using JWTs (or any other "algorithmically verifiable" token). This improves performance and enables architectures where for example you have a single auth server globally but multiple "functional" edge servers close to your users to serve out requests as soon as possible.
You can revoke tokens before they expire by distributing a token revocation list to front-ends. This involves some extra work, but presumably, the list of unexpired but revoked tokens will normally be short.
I might be wrong about it, but my understanding is as follows:
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).
I am no JWT expert, but I have found them useful for APIs rather than web apps accessed from the browser.
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).
But again, what's the difference between that and just having a moderately-long access token if you'll still be handing out access tokens for the lifetime of the refresh token?
Another comment mentioned it, but:
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.
> for the lifetime of the refresh token
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.
> 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?
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].
> 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?
No. Focus on “token” instead of “JWT.” JWT is just a standard.
Are you saying tokens can't be opaque?
This article confuses so many things that at first I thought it was making a completely different point. But let's go:
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.
Your refresh token does not need to be a JWT, it needs to be checked once in a blue moon and a server side round trip is OK
But I already pointed that out in my question. You didn't address my actual concern.
The access tokens go everywhere, to all services and are much more likely to be accidentally leaked / misappropriated. Refresh tokens are only used when talking (infrequently) to the access token minting service, so the scope of use is much much narrower.
So if I get this correctly, this is mostly useful for microservices and would be kinda pointless for a monolithic architecture?
Not even necessarily microservers. If you want a common authentication system used by lots of disparate services, token based Auth is a reasonable approach.
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.
I'm not making any judgement calls here in terms of how things are _actually_ done, or what the _actual_ risk exposure is, but...
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.
The point is JWTs can be validated independently on the server, no DB lookup is required. In distributed systems, that's the main benefit - they don't all need to talk to the auth server, just have a certificate that can be used to validate JWTs. This means they can't be practically invalidated per user though.
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".
You're missing that JWT were a bad idea in the first place.
Oldish but still largely relevant: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
It's articles like these that make things more confusing for folks who are learning about auth/session security. It's obvious the author has developed strong convictions without really understanding the subject.
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
This article can't explain what it's trying to hit at!
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.
> A problem is that a user can't revoke a JWT.
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.
> Once you make your JWT stateful, by storing it in a database there is no reason to use JWT.
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.
I guess it depends on how fast revoked sessions need to stop working and how many you have.
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?
This is exactly how many banks handle auth because 1.) jwt is mandated and 2.) so is session revocation.
> 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)
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.
You can trade for complexity—you can use revocation lists if you want to revoke JWTs. The revocation list of unexpired but revoked JWTs will generally be much shorter than the list of valid JWTs. You can push these lists to your front-ends, if that’s where you authenticate requests.
Far from trivial to implement, but it gives you some amount of performance and security at the same time.
In a single page application you have to access the JWTs with JavaScript. When we use cookies to implement sessions we have attributes like HttpOnly to prevent the cookie from being referenced by JavaScript code. In this case a XSS vulnerability would not be able to simply access the cookie and take over another users session.
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.
> In a single page application you have to access the JWTs with JavaScript.
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.
> > ...utilize JSON Web tokens (JWTs) for session handling instead of cookies.
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.
Presumably they mean session cookies.
Then why do people keep sending JWTs with only session tokens, which on the backend they treat like session cookies?
The problem isn’t so much JWTs for auth, it’s just where you are storing them.
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
Agreed. If security needs to extend past domain, you are not using the web in the way the web supports.
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.
> The answer here is just to store your session JWTs in HttpOnly secure cookies
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.)
The lack of logout and XSS are problems, but I ran into a couple apps that completely forgot to expire sessions due to lacking framework support. In nodejs's cookie-session and @google-cloud/connect-firestore sessions never expire. This issue impacts downstream software including, awkwardly enough, Google's Passkey demo apps. There isn't interest in fixing this.
Make sure your app is actually using a JWT framework, not a lesser version, and implements basic security practices.
It's interesting how much of an upward hill battle it has been for me to argue that JWT tokens need to be stored in cookies rather than LocalStorage. In my latest project, the lead backend dev is convinced it is insecure to store the accessToken in a HTTPOnly cookie, and that it HAS to be stored in LocalStorage.
I'm curious what were their reasons - it's hard to imagine how localStorage would be "more secure" than http only cookies.
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.
My best guess is that they are thinking of CSRF. With cookies, requests automatically carry the token, whereas with local storage you need to explicitly add the token. However, CORS does a lot to improve this situation. I note that CORS allows posting form data without pre-flight, but it is not immediately clear to me if posting a form cross domain will send cookies.
> but it is not immediately clear to me if posting a form cross domain will send cookies.
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.
This is what the SameSite setting on the cookie controls
It depends on the use case. For example, if your app uses WebSockets for all its data, authentication and access control and only uses HTTP for static files, you may not want the JWT to be sent to the server as a cookie with each HTTP request as it would be wasteful so localStorage may be more appropriate.
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.
But aren't HTTPonly coookies stored in a SQLite db, and have no password attached in Firefox or Chrome?
Both cookies and localStorage are usually stored in an SQLite db…
localStorage is totally accessible to any JavaScript running on the page, so your session can be completely hijacked and used later.
> so all it takes is a little injected script
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.
> If you can inject javascript, it's game over anyway.
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 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.
Then again, why bother with the tokens if you have XSS access as an attacker? I'd simply show the user a login prompt and take their password when they type it in.
> HTTPOnly cookies are safe from XSS attacks.
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.
What was their argument?
Short lived access token, and long lived refresh token.
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.
I've been exploring best practices for session storage, and it seems like using HTTP-only cookies the only secure choice. However, I've hit a roadblock when trying to implement a login feature within an iframe, especially with Safari disabling cookie functionality in iframes. This has left me pondering alternatives for secure user authentication within iframes. Has anyone encountered a similar challenge or found a workaround? I'd love to hear your insights and experiences!
I had a problem with cookies on iOS/safari, so we reached for the last hope: url query args.
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.
Keep in mind that urls end up in logs, that might well not be so well protected
In our case this is fine. The URL doesn't pass any claims. It is opaque client state bound to a specific identity which is validated by other means.
Particularly if you use cdns, tracing, analytics, etc.
Also, IIRC a parent frame can retrieve a child frame's current URL no matter what.
These are all bad arguments which I've heard many times. Once your front end is compromised with an XSS attack, it doesn't matter if you're using cookies with session IDs or JWTs... The attacker can use their malicious script running in the user's browser to make calls to your back end server on behalf of the user since the session ID would be sent along with the request inside the Cookie header.
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.
Here's a link to an old Stack Overflow question where it's stressed that JWT should be stored as cookies.
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.
It's mind-blowing how much stigma there is around native browser features. The web has been around for a long time and there are "out of the box" solutions for many things folks sometimes try to re-invent.
Just do not store JWTs in LocalStorage or any JavaScript accessible location. Use secured httpOnly cookies. Validate the JWT on server-side _stateless_. No need for a database. This idea is so good and it works! Just follow best security practice. If you don't, it is not the fault of the JWT. Bad blog article..
Yes, things like Keycloak and such follow _bad practice_. Still not the fault of JWT.
In a single page application it is necessary to access the JWT with JavaScript. Thats why it is so common to save it in the code directly or in the local storage. It is dangerous though, since a XSS vulnerability can be used to access the JWT. This would be totally different with a cookie that is stored with HttpOnly.
No, why? It is very often not necessary to make this accessible to JavaScript, except you are working with refresh tokens. But this is mostly not necessary and overused.
You do need a database if you want the ability to log users out server side. This is usually done through a second refresh token.
Weird article. Like others have said, it's mostly about XSS.
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.
It would be helpful if the post not only told you what to _not_ do (especially when it is a frequently done thing) but offered any sort of alternative.
Probably a generalization but in my experience many IT security people don't seem very pragmatic. "No you can't do that" but no alternative. "No don't use that cipher" but can't tell you the correct one. "Don't use equipment that doesn't receive firmware updates anymore and doesn't support newer encryption standards". "Don't allow mDNS" so no more printing from smartphones or presenting stuff from your laptop using Miracast? It gets tiresome really fast.
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.
> so no more printing from smartphones or presenting stuff from your laptop using Miracast? It gets tiresome really fast.
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.
Cookie based session logins like everyone used to use?
Not everyone can look back at a 10 year long career in the industry to draw inspiration from. Especially for junior engineers, pointing out alternatives (that feel obvious to you) would be important.
Can’t be used when embedding on third party sites though.
I work for ab EU government, and cookies are a no-go because of cookies directive, so we use JWT and auth the javscript engine, not the browser.
This leads to a multitude if problems, but who cares?
This makes no sense. The law didn't specify cookies specifically, it is agnostic about the technical implemention, surely?
Is this a clueless manager thing?
Not a manager thing, it is a consensus in at least one major EU government sweatshop.
Go figure.
>and cookies are a no-go because of cookies directive
haha, what?!
this is not true.
That's bullshit. Even ec.europa.eu (the European Commission's website - I have to login there from time to time) sets session cookies on my browsers. Either you've misunderstood what's asked of you, or your manager has, or someone higher up in your organization. But the "cookie directive" has never prevented anyone from using cookies altogether. You don't even need to ask for consent for a session cookie.
*.europa.eu websites have been known to infringe on EU rules forever. Shoemaker without shoes, you know.
That's a bold claim, any source?
PASETO?
Just store it in localStorage...
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.
Stop using "Stop using X" arguments, without offering an alternative.
Exactly. So anyone reading can suggest what's the alternative while still using JWT?
I mean how to do it properly?
LocalStorage is enough. The point of using a JWT is to avoid using session cookies. If you store the JWT in a cookie, what's the point? You can just use plain old session cookies. Discord keeps their tokens in localStorage and no riots have happened yet
Years ago this headline was being spammed all the time by some vendor that was pushing something, just like you’d see “hiring is broken” spammed by triplebyte every day for years.
Interesting. I thought the exact same thing but shrugged it off as far-fetched... but reading this makes me think twice.
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?
> Modern web applications (so called single page applications) often utilize JSON Web tokens (JWTs) for session handling instead of cookies
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?
> 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.
Terrible article. JWTs can be stored in cookies giving you httponly and samesite. If you have XSS vulnerability, what you store your JWTs or if you use JWT do sessions is the least of your concern. Every request to the domain still has cookies attached even with httponly set, that includes all the AJAX initiated requests. HTTPonly just prevents javascript from reading the cookie. It just prevents cookie theft.
In my opinion there are 2 good approaches:
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
> 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?
Cookies are sent with every request, including to every image or script file or style sheet etc etc. When sent as a separate header, you only set it to API requests.
You could use the Path prefix to only send to API endpoints where request has to be authenticated?
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.
Yeah, fair point, maybe could get some wins if serving assets from same domain, but probably should use a CDN for that on different domain
i really don’t understand how the article assumes jwt s are not stored in http only cookies. Jwts are an encoding method you can use them to implement a wide array of usecase and if they are used for sessions of course the security best practices for session cookies have to be met, that has nothing to do with jwts except that they require special thought compared to server side sessions when it comes to things like invalidation.
and when it comes to reading claims and other data from jwts, no one in its right mind would do that by making the session cookie available directly to js client code, so this is a total straw man argument. this is achieved either by making those available separately with means to validate signature but not usable as session token itself or/and by having a session endpoint on the server that reads and validates the jwt and responds with the data from it with the advantage of being able to also add additional session information that is not encoded in the jwt and only available on the server.
I agree, give everyone a session cookie with an encrypted session id, store JWTs in the http context for the session, make the cookie unreadable by js. If you need to read the properties from the token make an endpoint for that. For god's sake stop giving out the JWTs directly to the client.
"For security reasons, it is advisable for users to log out from a web application once they have completed their tasks."
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.
I think I am far less likely to be successfully phished (compared to earlier internet) now that I have a password manager programmatically checking domains to choose credentials. I wouldn't think everyone is doing that, but maybe iOS and Android have kinda nudged the majority to use password managers?
I agree that password managers are a huge win from a security perspective, but sadly I'm willing to bet they are still used by a tiny minority of people, even now they are built in to common operating systems.
And fewer still are going to avoid copying and pasting the password in from the password manager if the site doesn't match perfectly.
This seems still largely relevant to me: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
look at any web client based authentication system like firebase or amazon cognito from FAANG companies. Cognito by default stores it in local storage, and firebase stores in index db and local storage. You can switch to cookies, but it is not possible to set httponly flag because they are client based (js based). And that's the tip of the iceberg.
Hmm, our JWT tokens are recreated using a client secret with each request (OIDC connect PKCE) so article makes no sense.
Here I was expecting them to say "use PASETO". Suggesting that we use cookies instead is wild.
i have been telling teams for years that jwts are only good for one-off processes. they don't even bother to use jewes, sometimes there is no signature validation. no wonder broken authorization is the top 1 security problem
TLDR store session things in cookies with httponly set.
Basically nothing to do with jwts. More to do with xss
And set the SameSite attribute to strict to prevent CSRF
SameSite=strict is weird, because it means if someone follows a link to your we application they will be treated as logged out in the first page they interact with, then logged in on any subsequent navigations they make within your site.
Wouldn't SameSite=Lax work just as well to prevent CSRF? It prevents things like malicious forms and image links from other sites.
Yes, Lax is the option when you want preventing CSRF and nothing else.
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.
But then I need a cookie banner /s
You do NOT need a cookie banner to store a session ID or a JWT.
because these are not cookies, right?
No, because they are functional cookies, you don't need to ask for permission of those. I'm also deriving that you might be conflating the two things. JWT and cookies are two different things, but you can store JWTs in a cookie.
The law has never been about cookies themself but about tracking user and asking their consent for selling/sharing their identifying data for things unrelated to the displayed purpose of the site.
It is, in the minds of many.
And FYI it is not even a law.
You need it for local storage too if I’m not mistaken.
You don't need consent for strictly required cookies for functional purposes. Assuming the user actually logged in on your website, you don't need a banner.
This is an extremely low quality article.
No. Don't tell me what to do
Oh fuck off. This energy and thinking inside the appsec community (of which i am a working member) is the reason the local _pizza company_ feels the need to have a 12 hour timeout on my mobile phone. Just implement a decent content security policy and then you can have the best of both worlds: stateless backend without having the (incredibly minor) risk of having your jwt token stole on my goddamn pizza website.
Thanks. Really. This attitude of "if it's not perfect in face of some ridiculous scenario, it's useless" is why people go full "one password for everything, hanging on my screen". Each time someone falls into one of these absurd usability hazards there's a risk you loose them for everything security-related. And when something bad happens security professionals go "oh, but it is YOUR fault" .. yeah, thanks for nothing.
Your job is to solve problems. In the real world. For real people. Start doing it.
Meanwhile, my Gmail linked to multiple other accounts is logged in to for months at a time.
TLDR: You can split the JWT into 3 parts and store them differently in cookies to keep the _payload_ accessible in JavaScript and make the _signature_ inaccessible from the web app.
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.
Ah, here we go again
It's still a bit weird how there's no universal gold standard way to handle authentication with websites. JWT is the closest there is, and there are still enough open ends with it for there to be as many ways to implement it as there are developers.
> there's no universal gold standard way to handle authentication with websites
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.
Not sure if I could bring myself to call JWT for website authentication the "gold standard". I'd give the title to HTTP Basic Auth first.