Discussion on: Please Stop Using Local Storage

paztek profile image
Matthieu Balmes

While some of your points are correct, suggesting to use sessions and cookies instead of JWT is missing the point.
People using JWTs are most probably running a SPA and communicating with a pure stateless API. We don’t want to hear about sessions in this case.

Storing a JWT in the cookies is perfectly OK and it has the advantage of not needing custom JS code to pass it to each HTTP request to your backend.
But in some situations, like when your API is also used by your mobile app and it requires the "Authorization Bearer xxx" header instead of a cookie or when you’re making HTTP requests to multiple backends but with same JWT, it’s convenient to have your JWT in localStorage instead.

Also, most modern JS frameworks (React, Angular, ...) nowadays produce concatenated and minified vendor scripts so you don’t have to include single scripts from third party locations.

mindlace profile image
Ethan Fremen

I think you’re misreading him. He doesn’t say “don’t use JWT” he’s just saying “don’t stick your JWT in local storage”.

Yes, it’s convenient. It’s also convenient to just leave the key to my house in the lock, but it means anyone who can get to the door can get in.

mighub profile image

JWTs are insecure by design

Considering JWT are most likely used for security, it is pretty much urging not to use them. Unless you need a token for accessing public data.

Many people don't realize that JWTs are essentially the same thing as a username/password.

This one is a stretch and if you go this way, then that is true for pretty much everything used for security nowadays. It is true for cookies. It is true for SSH keys, etc. Any written access that replaces your password can be exploited in a way or another.

Your example with keys is interesting because even if it is not bulletproof, then you are better off typing your password on each request. Except it's not practical.

Don't get me wrong, the article is interesting and everybody should understand the danger of all these practices. But then people will be waiting for the alternative. And mentioning an alternative is the worst thing to do because people will prove you wrong, because nothing is unbreakable.

You're better off warning and help them make sure they are making their best to secure the way they use the current tool.

rdegges profile image
Randall Degges Author

Hey, this is a great point. But hear me out.

Let's say you want to store a JWT in a cookie -- that's fine. BUT: the purpose of JWTs is to be stateless, right? Cookies are capped out at 4k, which means the JWT needs to be < 4k for this to work.

Most stateless JWTs are > 4kb (this is quite easy to test, serialize a user from your DB into a compact'd JWT and look at the byte size -- I've done this on a lot of sample apps and in almost every case except the most simple the JWT is > 4kb). In this scenario, you've basically got to use local storage.

Instead of doing that: why not just use a session cookie as I recommended? The downside is that you need to manage a cache on the API side, but this is easily doable. If you're using JWTs anyway (with local storage, let's say), you STILL NEED to have centralized sessions in some way to manage revocation.

JWTs are insecure by design: they cache authentication/authorization data, so in order to work around their speed-vs-security tradeoff you've got to manage a revocation list centrally no matter what: otherwise you end up in situations where revoked permissions/data are being allowed through -- a poor scenario.

Now, let's talk about your point about using the Authorization header. I hear this a lot: I think I will write another article about it as it's a common misconception.

There is NO DIFFERENCE between sending auth data to a server in the Authorization header (with a bearer token) vs sending it to the server using a cookie. COokies are just HTTP headers as well. The difference is a few characters:

Authorization: Bearer <token>
Cookie: session=<signed(id)>

Any server can parse either header. While you may not want to for some reason, there is nothing technical or bad about using a cookie for this purpose.

I'd argue that the ONLY reason you need to use a JWT for auth is if you're using OpenID Connect, as for some reason the authors of the spec decided to standardize on JWTs for the ID token. In this case, using the JWT and putting it into an Authorization is actually required -- outside this case? Not at all.

And re: scripts being minified/included... This does not apply to the biggest use case I mentioned in the article: third party tools. Things like GA, Optimizely, Kissmetrics, and all the other marketing/ad network tools that most companies use.

There are definitely scenarios in which you can make yourself safe from XSS, but those situations are increasingly rare.

As always, this article is only meant to provide guidance to the 99% of people who do this stuff blindly without thinking about it. In your case, you likely know the risk profile for what you're doing and can decide whether or not you want to take the risk.

moodysalem profile image
Moody Salem

Content-security-policy for XSS + subresource integrity hashes for third party hosted libraries, or just compile in the third party tool like google analytics, e.g. npmjs.com/package/analytics

There are many improvements being made to the security of single page applications (HSTS en.wikipedia.org/wiki/HTTP_Strict_..., certificate transparency en.wikipedia.org/wiki/Certificate_...) that make storing sensitive information in JS about as safe as storing them in a mobile app.

togakangaroo profile image
George Mauer

you've got to manage a revocation list centrally

This might be true for Google or Amazon but is not actually true for the majority of applications out there I would argue.

You find out a token is compromised? Just regenerate your signing key. Yes every application user will have to log in again but that is a perfectly acceptable action for most applications.

dkamer profile image
David Kamer

JWTs aren't inherently unsafe. I like that JWTs are signed by a server side secret, and I like the flow that creates during auth. Depending on how secure the app needs to be, I've even stored user agent and remote address info in a JWT, signing it with a user specific secret. I had the JWT checked for all of that as accurate against headers and then refreshed the sign/verify secret if the JWT had data that was bad in it.

Most apps can get away with an expiry on a JWT and increase performance, this decreasing energy usage, this decreasing environmental impact of your code.

The CIA of information (Confidentiality, Integrity, Availability) is held strong. OP only focuses on Confidentiality for some reason.

inf3rno profile image
Jánszky László Lajos

I don't think you understand that APIs don't scale with sessions maintained on server side. That's why we no longer use session cookies. Storing JWT in http only cookies is not perfect, but ok from scalability perspective.

jondubois profile image
Jonathan Gros-Dubois

One problem with the article is that the words 'never' and 'always' are used way too often.

Also, the article says that localStorage is insecure quite often but doesn't give any evidence or examples.

I would argue that localStorage is as secure as cookies (including httpOnly cookies).

localStorage uses essentially the same security policy as cookies; one of its core principles is that a domain cannot access localStorage data that was created under a different domain so there is no chance that a website could steal data from a different website.

Also, httpOnly cookies do not make your site any less vulnerable to XSS attacks; if the attacker manages to inject a malicious script into your front end, then they can use that script to make HTTP requests to your server (directly from the victim's browser) and your precious httpOnly cookie (containing the user's valid session ID) will be attached to every request so the server will service them without suspecting anything.

The only real difference is that if the token (e.g. JWT) is in localStorage then the attacker can steal the token to use later (same goes for regular non-httpOnly cookies BTW)... Which is hardly a convenience because it's more advantageous for the attacker to carry-out the attack in-place from the victim's browser rather than from the attacker's own machine (thus allowing their IP to be traced directly).

Also, with JWTs, it's good practice to set short expiry dates. If you're using WebSockets you can even issue JWTs with 10 minute expiry (for example) and re-issue a new one automatically every 8 minutes if the user is still connected and logged in; then when the user logs out or becomes disconnected; their last issued JWT will become invalid in only 10 minutes (at which point it becomes completely useless to an attacker).

Also, it's not recommended practice to store large amounts of data inside a JWT because of the overhead of having to send it with every request/connection.

Thread Thread
ilusionlinux profile image
Luis Morales

woow this is one of the best comments in this post.

Thread Thread
rugk profile image

Indeed the original article totally misses the point that local storage is by no means less secure than any other part of your website. If you have an XSS, you are flawed. That's actually the reason why XSS attacks are so serious/bad.

Thread Thread
ivankleshnin profile image
Ivan Kleshnin

If you fetch from the browser, you don't get cookies. You have to add {credentials: "include"}. And that requires a whitelisting on the server. So no, it's no so easy to get httpOnly cookie content in browser as you describe. It requires a TRACE method or other known vulnerability or bug to expose them. Pls. prove me wrong if you think otherwise.

Other than that, I agree with your point. When an app has an XSS injection there are tons of attack vectors a hacker can take. And yes, localStorage is almost the same as cookies security-wise. This article is mostly a FUD, unfortunately.

Thread Thread
maciek134 profile image
Maciej Sopyło

His point was that you don't need to read the cookie if you can send requests when the user is on the compromised website.

bhaibel profile image
Betsy Haibel

Here's a question: in what contexts do SPAs need to use bearer token auth, and is this truly an ideal?

There's only one class of SPAs which CAN'T use cookie auth -- namely, SPAs using a statically served application shell. This is an architectural decision with a lot of tradeoffs to it. On the plus side S3 is cheap, there's a certain theoretical purity to having your web frontend go #serverless, and you only need to maintain one form of API authentication. On the minus side, you have to greenspin literally everything that the browser gives you for free.... like the nice security properties of cookie auth.

There's nothing wrong with maintaining a simple, stupid edge service that exists to maintain sessions & proxy requests to your API. It feels pretty oldschool, but it works pretty well. And if (when) your web frontend winds up wanting a sliiiiightly different API than your mobile app, having an edge service already in place makes that trivial! Most existing SSR frameworks (Next, Nuxt...) can serve this purpose without too much modification of an existing app-shell-style SPA.

elasticrash profile image
Stefanos Kouroupis

You can work around statically served apps and authentication if you use a concept of an API gateway like Kong. I my case statically served apps are only allowed to go through a Kong like service...in which we can control authentication, rate limiting etc