144 comments

[ 3.0 ms ] story [ 220 ms ] thread
(comment deleted)
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.
TLDR store session things in cookies with httponly set.

Basically nothing to do with jwts. More to do with xss

But then I need a cookie banner /s
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.
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.

And set the SameSite attribute to strict to prevent CSRF
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.

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.
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.
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'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.
This is what the SameSite setting on the cookie controls
> 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.

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.

> 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.

> 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.

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.
> 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.

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.

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.
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.
> 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.

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.
Cookie based session logins like everyone used to use?
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?

>and cookies are a no-go because of cookies directive

haha, what?!

this is not true.

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.

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?
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.
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.

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?

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
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.

(comment deleted)
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
Particularly if you use cdns, tracing, analytics, etc.

Also, IIRC a parent frame can retrieve a child frame's current URL no matter what.

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.
This 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.
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.

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".

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?
> 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.

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.

(comment deleted)
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).

> 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.

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.
(comment deleted)
> 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].

[0]: https://www.biscuitsec.org/

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.

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.

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.
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.
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.

[1] https://github.com/expressjs/cookie-session

[2] https://github.com/googleapis/nodejs-firestore-session

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.

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.
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.

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.

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.
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.
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.

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
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. :)

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.

(comment deleted)
This is an extremely low quality article.
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
"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.
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