Seriously. Just stop it already.
I don't know what it is, exactly, that drives so many developers to store session information in local storage, but whatever the reason: the practice needs to die out. Things are getting completely out of hand.
Almost every day I stumble across a new website storing sensitive user information in local storage and it bothers me to know that so many developers are opening themselves up to catastrophic security issues by doing so.
Let's have a heart-to-heart and talk about local storage and why you should stop using it to store session data.
What is Local Storage?
I'm sorry if I was a bit grumpy earlier. You don't deserve that! Heck, you might not even be familiar with what local storage is, let alone be using it to store your session information!
Let's start with the basics: local storage is a new feature of HTML5 that basically allows you (a web developer) to store any information you want in your user's browser using JavaScript. Simple, right?
In practice, local storage is just one big old JavaScript object that you can attach data to (or remove data from). Here's an example of some JavaScript code that stores some of my personal info in local storage, echoes it back to me, and then (optionally) removes it:
// You can store data in local storage using either syntax
localStorage.userName = "rdegges";
localStorage.setItem("favoriteColor", "black");
// Once data is in localStorage, it'll stay there forever until it is
// explicitly removed
alert(localStorage.userName + " really likes the color " + localStorage.favoriteColor + ".");
// Removing data from local storage is also pretty easy. Uncomment the lines
// below to destroy the entries
//localStorage.removeItem("userName");
//localStorage.removeItem("favoriteColor");
If you run the JavaScript code above in your browser on a test HTML page, you'll see the phrase “rdegges really likes the color black.” in an alert message. If you then open up your developer tools, you'll be able to see the that both the userName
and favoriteColor
variables are both stored in local storage in your browser:
Now you might be wondering if there's some way to use local storage so that the data you store is automatically deleted at some point and you don't need to manually delete every single variable you put in there. Luckily, the HTML5 working group (shout out!) has your back. They added something called sessionStorage to HTML5 which works exactly the same as local storage except that all data it stores is automatically deleted when the user closes their browser tab.
What's Cool About Local Storage?
Now that we're on the same page about what local storage is, let's talk about what makes it cool! Even though the whole point of this article is to dissuade you from using local storage to store session data, local storage still has some interesting properties.
For one thing: it's pure JavaScript! One of the annoying things about cookies (the only real alternative to local storage) is that they need to be created by a web server. Boo! Web servers are boring and complex and hard to work with.
If you're building a static site (like a single page app, for instance), using something like local storage means your web pages can run independently of any web server. They don't need any backend language or logic to store data in the browser: they can just do it as they please.
This is a pretty powerful concept and one of the main reasons that local storage is such a hit with developers.
Another neat thing about local storage is that it doesn't have as many size constraints as cookies. Local storage provides at least 5MB of data storage across all major web browsers, which is a heck of a lot more than the 4KB (maximum size) that you can store in a cookie.
This makes local storage particularly useful if you want to cache some application data in the browser for later usage. Since 4KB (the cookie max size) isn't a lot, local storage is one of your only real alternative options.
What Sucks About Local Storage
OK. We talked about the good, now let's spend a minute (or two!) talking about the bad.
Local storage is soooo basic. WHEW. I feel better already getting that off my chest. Local storage is just an incredibly basic, simple API.
I feel like most developers don't realize just how basic local storage actually is:
It can only store string data. Boo. This makes it pretty useless for storing data that's even slightly more complex than a simple string. And sure, you could serialize everything including data types into local storage, but that's an ugly hack.
It is synchronous. This means each local storage operation you run will be one-at-a-time. For complex applications this is a big no-no as it'll slow down your app's runtime.
It can't be used by web workers =/ This means that if you want to build an application that takes advantage of background processing for performance, chrome extensions, things like that: you can't use local storage at all since it isn't available to the web workers.
It still limits the size of data you can store (~5MB across all major browsers). This is a fairly low limit for people building apps that are data intensive or need to function offline.
Any JavaScript code on your page can access local storage: it has no data protection whatsoever. This is the big one for security reasons (as well as my number one pet peeve in recent years).
To keep it short, here's the only situation in which you should use local storage: when you need to store some publicly available information that is not at all sensitive, doesn't need to be used in a high-performance app, isn't larger than 5MB, and consists of purely string data.
If the app you're using doesn't fit the above description: don't use local storage. Use something else (more on this later).
Why Local Storage is Insecure and You Shouldn't Use it to Store Sensitive Data
Here's the deal: most of the bad things about local storage aren't all that important. You can still get away with using it but you'll just have a slightly slower app and minor developer annoyance. But security is different. The security model of local storage IS really important to know and understand since it will dramatically affect your website in ways you may not realize.
And the thing about local storage is that it is not secure! Not at all! Everyone who uses local storage to store sensitive information such as session data, user details, credit card info (even temporarily!) and anything else you wouldn't want publicly posted to Facebook is doing it wrong.
Local storage wasn't designed to be used as a secure storage mechanism in a browser. It was designed to be a simple string only key/value store that developers could use to build slightly more complex single page apps. That's it.
What's the most dangerous thing in the entire world? That's right! JavaScript.
Think about it like this: when you store sensitive information in local storage, you're essentially using the most dangerous thing in the world to store your most sensitive information in the worst vault ever created: not the best idea.
What the problem really boils down to is cross-site scripting attacks (XSS). I won't bore you with a full explanation of XSS, but here's the high level:
If an attacker can run JavaScript on your website, they can retrieve all the data you've stored in local storage and send it off to their own domain. This means anything sensitive you've got in local storage (like a user's session data) can be compromised.
Now, you might be thinking “So what? My website is secure. No attacker can run JavaScript on my website.”
And that's a reasonable point. If your website is truly secure and no attacker can run JavaScript code on your website then you are technically safe, but in reality that is incredibly hard to achieve. Let me explain.
If your website contains any third party JavaScript code included from a source outside your domain:
- Links to bootstrap
- Links to jQuery
- Links to Vue, React, Angular, etc.
- Links to any ad network code
- Links to Google Analytics
- Links to any tracking code
Then you are currently at risk for having an attacker run JavaScript on your website. Let's say your website has the following script tag embedded inside it:
<script src="https://awesomejslibrary.com/minified.js"></script>
In this case, if awesomejslibrary.com is compromised and their minified.js
script gets altered to:
- Loop through all data in local storage
- Send it to an API built to collect stolen information
... then you are completely screwed. In this situation the attacker would have easily been able to compromise anything you had stored in local storage and you would never notice. Not ideal.
As engineers, I think we're frequently susceptible to thinking that we would never embed third-party JavaScript in our websites. But in the real world, this scenario rarely plays out.
At most companies, the marketing team directly manages the public website using different WYSIWYG editors and tooling. Can you really be sure that nowhere on your site are you using third-party JavaScript? I'd argue “no”.
So to err on the side of caution and dramatically reduce your risk for a security incident: don't store anything sensitive in local storage.
PSA: Don't Store JSON Web Tokens in Local Storage
While I feel like I made myself clear that you should never ever store sensitive information in local storage in the previous section, I feel the need to specifically call out JSON Web Tokens (JWTs).
The biggest security offenders I see today are those of us who store JWTs (session data) in local storage. Many people don't realize that JWTs are essentially the same thing as a username/password.
If an attacker can get a copy of your JWT, they can make requests to the website on your behalf and you will never know. Treat your JWTs like you would a credit card number or password: don't ever store them in local storage.
There are thousands of tutorials, YouTube videos, and even programming classes at universities and coding boot camps incorrectly teaching new developers to store JWTs in local storage as an authentication mechanism. THIS INFORMATION IS WRONG. If you see someone telling you to do this, run away!
What to Use Instead of Local Storage
So with all of local storage's shortcomings, what should you use instead? Let's explore the alternatives!
Sensitive Data
If you need to store sensitive data, you should always use a server-side session. Sensitive data includes:
- User IDs
- Session IDs
- JWTs
- Personal information
- Credit card information
- API keys
- And anything else you wouldn't want to publicly share on Facebook
If you need to store sensitive data, here's how to do it:
When a user logs into your website, create a session identifier for them and store it in a cryptographically signed cookie. If you're using a web framework, look up “how to create a user session using cookies” and follow that guide.
Make sure that whatever cookie library your web framework uses is setting the
httpOnly
cookie flag. This flag makes it impossible for a browser to read any cookies, which is required in order to safely use server-side sessions with cookies. Read Jeff Atwood's article for more information. He's the man.Make sure that your cookie library also sets the
SameSite=strict
cookie flag (to prevent CSRF attacks), as well as thesecure=true
flag (to ensure cookies can only be set over an encrypted connection).Each time a user makes a request to your site, use their session ID (extracted from the cookie they send to you) to retrieve their account details from either a database or a cache (depending on how large your website is)
Once you have the user's account info pulled up and verified, feel free to pull any associated sensitive data along with it
This pattern is simple, straightforward, and most importantly: secure. And yes, you can most definitely scale up a large website using this pattern. Don't tell me that JWTs are “stateless” and “fast” and you have to use local storage to store them: you're wrong!
Non-String Data
If you need to store data in the browser that isn't sensitive and isn't purely string data, the best option for you is IndexedDB. It's an API that lets you work with a database-esque object store in the browser.
What's great about IndexedDB is that you can use it to store typed information: integers, floats, etc. You can also define primary keys, handle indexing, and create transactions to prevent data integrity issues.
A great tutorial for learning about (and using) IndexedDB is this Google tutorial.
Offline Data
If you need your app to run offline, your best option is to use a combination of IndexedDB (above) along with the Cache API (which is a part of Service Workers).
The Cache API allows you to cache network resources that your app needs to load.
A great tutorial for learning about (and using) the Cache API is this Google tutorial.
Please Stop Using Local Storage
Now that we've had a chance to talk about local storage, I hope you understand why you (probably) shouldn't be using it.
Unless you need to store publicly available information that:
- Is not at all sensitive
- Doesn't need to be used in an ultra high performance app
- Isn't larger than 5MB
- Consists of purely string data
... don't use local storage! Use the right tool for the job.
And please, please, whatever you do, do not store session information (like JSON Web Tokens) in local storage. This is a very bad idea and will open you up to an extremely wide array of attacks that could absolutely cripple your users.
Have a question? Shoot me an email.
Stay safe out there =)
NOTE: For those of you who made it this far who are wondering why I didn't specifically call out Content Security Policy as a way to mitigate the effects of XSS, I specifically chose not to include this because it cannot help in the situation I described above. Even if you use CSP to whitelist all third-party JavaScript domains, that does nothing to prevent XSS if the third party provider is compromised.
And while we're at it: subresource integrity (while cool) is also not a global solution to this issue. For most marketing tools, ad networks, etc. (which are by far the most commonly used types of third-party JavaScript), subresource integrity is almost never used as the providers of those scripts want to change them frequently so they can silently update functionality for their users.
UPDATE: I'm not the only one who thinks you should never store anything sensitive in local storage. So does OWASP:
... In other words, any authentication your application requires can be bypassed by a user with local privileges to the machine on which the data is stored. Therefore, it's recommended not to store any sensitive information in local storage.
Top comments (197)
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.
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>
vs
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.
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.
woow this is one of the best comments in this post.
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.
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 gethttpOnly
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. This article is mostly a FUD, unfortunately.
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.
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.
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.
First of all, you’ll need hundreds, if not even thousands of requests per second until your API stops scaling. Second thing, sessions can be serialized and stored in an external data store such as Redis or even Postgres. This way, you may easily scale up or down your API since you moved session state outside of your service. Yes, you will need to take a look at that data store each time when you want to check session validity, but this is very fast. The issue is that session data store becomes a single point of failure, but so is modern authentication service such as Keycloak. If Keycloak goes down, users will start to log out as soon as their JWT’s start expiring since it will be unable to issue new JWT’s. Third and final thing, a common sense. As I already wrote, it will take hundreds or thousands of requests per second before you need to scale to additional instance. In my experience, an app which handles 200-500 requests generates so much money that you can afford to rewrite your authentication layer from scratch implementing your fancy stateless tokens.
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.
JWT is inherently unsafe for the sole purpose it cannot be immediately invalidated. The only way to force invalidation is to change a signing key. If you’re blacklisting JWT’s, then you’re using it wrong. Blacklisting requires preserving a state and checking against it. If you’re saving them, then you’re using them wrong (there’s no a plus in scalability over sessions). If you’re using refresh tokens, then again, you have a state. Plus, it leads to number of bad practices, such as storing them in local storage which can be exposed to XSS attacks, use of long lived JWT’s, or storing sensitive informations in their payload. What would be the correct way to use them? Probably by having a dedicated authentication server like Keycloak which can use short lived JWT’s in combination with refresh tokens and key rotation. But, do you really need such a complicated architecture in the end of the day?
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.
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.
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.
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.
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.
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
There is another class of SPAs that cannot use cookies: Those that are supposed to work inside an iframe. Third party cookies are denied in any third party http request in Safari already and Chrome is coming. Firefox did the right thing and still allow them while preventing unwanted tracking.
Another great resource to look at is the BFF pattern, worth looking into to explore the idea in this post further.
docs.duendesoftware.com/identityse...
youtube.com/watch?v=UBFx3MSu1Rc
Good article and good awareness. However....
Quote: "If an attacker can run JavaScript on your website, they can retrieve all the data you've stored in local storage and send it off to their own domain."
Edit: "If an attacker can run JavaScript on your website, you're ka-pooched, and a local jwt is the least of your worries."
😂😂😂
"If an attacker can run JavaScript on your website, you're ka-pooched, and a local jwt is the least of your worries."
Have you ever used jQuery or any other 3rd party JS library on your site? How certain are you that that 3rd party does nothing malicious? How certain are you that 3rd party library hasn't been compromised?
Not even the library: if you're using one, you're probably pulling from a CDN right? How certain are you that CDN hasn't been compromised to serve a different version of the library than what you think you're getting?
"How certain are you that that 3rd party does nothing malicious?"
I'm not very certain. But you're missing the point. If the library is tainted, what can it do? Much, much worse than the worst case scenario here. That's the point. It's like duct tape to fix the titanic. It's like humans in a Marvel movie. Useless.
Just like Xavier, a compromised lib can mind-control the poop out of your app and wreak havoc for your users, and there's nothing you can do about it. XSS is a security rabbit hole.
When you understand what a tainted lib can do, you realize how trivial this article's point is. That is my only point.
Part of the problem with the proposed solution in this article is that cookies are tied to a specific domain. That makes it much harder and clunky to use third party security services like Auth0. Also, scalability does become a MUCH harder issue with server-side sessions; I don't care what this article says.
Best idea to overcome your tainted paranoia is to use script's
integrity
attribute, eg:Good article but I disagree.
IMHO storing JWT in localstorage is ok. I mean, what is actually not ok given your examples is to be vulnerable to XSS attacks, or using third party scripts/infrastructure you don't trust, in both cases the local stored jwt is the least of your worries –as crazy4groovy points out–.
Here's the thing, we need to store something on client side so they don't have to type the user/password each time we're making a request to the server and yeah EVERYTHING on client side is meant to be insecure. But, does it mean that, for example, browsers shouldn't let users save user/password because third party extensions, etc. can access to them? Hell no.
Yes, if you have the jwt you can access, but is it that bad? That's why they're called bearer tokens: you have one, you can access then. It's like a check payable to cash: they are payable to anyone who presents them. Does it mean that the bank should hire a bodyguard for you? Probably not.
Okeey! I digress, but the point is we need a balance between security and ease. No way I'm using cryptographically signed cookies for storing just a user token for a social network. However, if I were a bank website I would do exactly this.
On the other hand, I agree with the sensitive data point. There is no need for storing sensitive data like addresses or credit cards into the localStorage.
Sorry for the long talk, too much text for a non native speaker like me:)
Hey! Thanks for the comment.
Storing a JWT in local storage is fine -- the point of this article is to explain to the 99% of developers who do this without thinking about it why it is bad and not recommended by OWASP and security people.
Obviously, if you are aware of the risks and choose to do so anyway, that's fine.
The tradeoff of using a JWT really comes down to this: speed vs security. JWTs are basically opting for speed (and a very minimal amount of speed, I might add) over security.
JWTs were not designed to be used for authentication or authorization data. They were designed to be used as one-time, short-expiration tokens to pass signed data from A->B. Using them as a way to "securely" store session data is abusing them in ways that are directly contrary to a secure mindset.
I guess if I could summarize my thoughts about your comment, I'd say this:
IMO, using session cookies is not only faster/more secure, but far simpler and safer for 99% of developers to use. If you're in the 1% who knows what you're doing and is willing to make the tradeoff, go for it. But for 99% of people out there, it's a bad idea.
Maybe it wasn't the intention but I feel that this article is contributing to hysteria around JWTs/localStorage.
I do software consulting related to an open source project I created and now I feel that I keep having to explain to my clients/companies over and over again why using a cookie would not make things more secure in their case.
I've even been asked to re-implement a perfectly secure authentication system because a security firm that my client was using recommended that they should stop storing their JWTs inside localStorage and that they should store it in the app memory as a global variable instead (as if that was any safer from XSS) - Also the JWTs had a 10 minute expiry so quite safe.
The problem is now that a lot of security consultancies are full of people who like to use articles like this to make blanket decisions on behalf of their clients in order to save them the ordeal of actually having to think about each company's case on an individual basis.
I just want to jump in real quick as one of those people who like to use articles like this to make blanket decisions. A few points as why security people say not to store session data in JWT and LocalStorage. Out of the box yes LocalStorage is more secure than a cookie for session data, however with the optional flag SameSite cookies are now equal to LocalStorage with built-in anti CSRF protections from the browser. Adding the HTTPOnly flag bring cookies to a higher level than LocalStorage. This is because now client-side JavaScript cannot access the cookies. The last part is where the recommendation to use cookies over LocalStorage is made in relation to potential exposure from XSS. Outside of XSS or vulnerabilities in the browser itself both are limited to exposure from physical access on the client-side in a general risk perspective.
The biggest risk for both options from a security perspective is XSS and while some may be confident in their ability to not introduce XSS, I can say that even the most experienced developers can have a hard time preventing XSS. I won't dive into too specific details but a shockingly common bypass to a lot of anti-XSS solutions is to switch the method from GET to POST or POST to GET in a request. Some popular frameworks even allow submitting GET parameters in the body which is completely wrong from the RFC point of view. The pervasiveness of XSS is why security people make the recommendation, we like to think in layers so if XSS is somehow introduced into the application you still have the layer from HTTPOnly on the cookie verse no additional layer on the LocalStorage side.
TLDR: LocalStorage out of the box is less insecure, but cookies offer more security than LocalStorage when done right. Security people aren't blindly making the recommendation there is a reason.
I would argue that using an httpOnly cookie doesn't add any security. At best you could say that it might make it slightly less convenient for an attacker to carry out the XSS attack.
I wrote a more detailed technical explanation here: dev.to/jondubois/comment/373l
Concur. There's no threat modeling that I could think of that would hold up httpOnly as being a significant factor if the threat vector up to that point has already leveraged XSS - so your local JS context is already 0wned - at this point, the exploit code just needs to directly execute from the compromised browser instead of sending the auth token to a remote server to be exploited from there. Considering the local context is already compromised, that hardly seems more than an inconvenience to the attacker, as jondubois indicated.
Yep, it seems to me that people started working with microframeworks on nodejs etc. thinking they understood what they were doing, then realized they're missing all the convenient features they used to have and didn't have a clue of how they were implemented so they just picked some things that sounded cool (JWT & localStorage) and went with those without even a single second of thought as to "why isn't everyone doing this?" or similar.
Secure information, such as session information, should almost universally be stored in
httpOnly
&secure
cookies, potentially withSameSite=strict
. I just don't see any reason for JS to touch the cookies in general, just use a/me
endpoint or similar to check if you're logged in and to fetch your user's information so you don't need to touch the secure cookies.The
/me
endpoint solution doesn't work with a stateless API.Can you clarify what you mean by a "stateless" API? If I'm reading you right, the concept seems kind of nonsensical to me. I'm going to respond to what I think you're saying, but I'm worried that I'm constructing a strawman in doing so.
I see three worlds in which a
/me
endpoint wouldn't be "stateless." The first is a world in which the current user is considered a form of state. Whether that's true in some absolute sense is an abstract philosophical question. The more practical question: if we're considering tracking the current user (ie "performing authentication") to be antithetical to a stateless API, then how are we supposed to then perform authorization? Is everyone just supposed to be allowed to do/see everything?The second: if you're using a
/me
endpoint to retrieve user data, in lieu of retrieving it from a cookie, presumably some earlier interaction has logged the user in. I suppose there's implicit state in the fact that those two interactions must occur in order. But even if we're using bearer token auth, that token needs to have been generated somehow. Complaining about the presence of that state doesn't seem very practical to me.The third: cookies are inherently stateful because they require session data to be present on the server. This is kind of true? But if we want to use a bearer token instead, presumably we need to have generated it. We can't generate secure bearer tokens idempotently or reversibly, so we need to store some kind of data about the bearer token on the server... which to me is not philosophically all that different from creating a session?
Anyway, again, I feel like I may well be arguing against a strawman here! If there's some fourth definition I haven't considered, I'd love to hear how you're defining stateless APIs and why you feel that that definition produces more useful/usable API designs.
Not exactly true. Many cookies use signatures or encryption to make the data trustworthy for the server, so there does not need to be the somewhat traditional "sessions" list in a database.
Cool, I didn't know that! How does credential revocation work in that context? Or do you just maintain a really short validity window?
Well, it doesn't necessarily work. One way is to keep short validity, another is to tie it to e.g. the last update of the relevant user's password, or similar - you wouldn't have to check the contents of the package, just that it's been signed after the password has last changed.
There are lots of little options you could use, but it's not always necessary.
I think lietu has just described JWT in a cookie ...
I don't really understand what your thinking of stateless is here. If your cookie holds all the necessary data, e.g. your user information in encrypted form or JWT style tokens or similar, then your backend server really needs to know no other "state".
To be clear, you're suggesting using cookies for authentication in a REST API? In my experience this is not a successful pattern and certainly doesn't conform to some of the core principles of REST.
It does conform to REST. There's nothing not RESTFUL about cookies.
Cookies are purely an HTTP header. When a cookie is sent from a browser->server it is sent via the
Cookie: name=value
format. This is it.What you're comparing it to is the Authorization header, which looks like this:
Authorization: ...
. There is NO difference whatsoever here except for the name of the header.This is a really common misconception. I've started replying to comments (this article really blew up while I've been busy working...) but I plan to write a more in-depth article covering some of the arguments I only briefly mentioned here in more depth.
On top of that, not all APIs have to be RESTful and assumed to be accessed by 3rd parties.
If it's your own application, you can still make pretty RESTful APIs, using cookies, and not have to worry about the difference between using
Authorization: ...
headers vs.Cookie: ...
headers.Authorization:
header is great when you assume the client is a non-browser application, and you can cater to those easily with your middleware. For your own browser applications cookies are better.Keep in mind that some sandbox environments don't allow arbitrary headers to be set or even cookies to be added, but they do support the RFC for Authorization headers. So, it may seem like the same thing, but when your local API (e.g. WkWebView had (has?) this as a limitation) - then you're going to have to use it that way.
So what you're trying to say is that since some systems are limited, it's ok to use a bad practice everywhere?
Of course you're allowed to work with the limitations of your system (though finding a less idiotic system is a better choice), but that doesn't mean you should use the bad practice from the bad system when working with good systems or systems you have control over and can fix.
Good article, but the language is too strong. You made a few good points but also some arguments are controversial.
I am not sure if using authentication cookie for API is a common pattern out there. I haven't seen many APIs using Cookie for authentication. Cookie and Authorisation are designed for different purposes. Yes, they are just different names behind the scene but applications treat them differently. Authorisation header is not automatically pre-filled by browsers while cookie is. That means using Cookie as authentication you're prone to CRSF. That's why we use Authorisation header to avoid CRSF as much as possible.
And last point, no one puts a few MB of data in a JWT token.
I hope nobody is putting a few MB of data in my localstorage either. If you use Authorization header, the token can be extracted by XSS etc. and sent to malicious servers and they can then use it however they wish.
If you use a
secure; httpOnly
cookie it can't be stolen by malicious JS and is bound to your browser's security model. Add to that proper CSRF tokens, and e.g.sameSite=strict
and you've got a decent system.Why do you assume I have access to a an endpoint where I can fetch this user's information? This user's claims could come from a trusted 3rd-party, and their claims are verified on my end by their pubkey.
Thanks for introducing me to IndexedDB. I had been using localStorage in a webapp and IDB sounds like a much better solution.
IDB is great for some use cases =)
Hey Randall,
I definitely agree that storing session information in
localStorage
is a bad idea. Secure, HTTP only cookies are the way forward for that.I just wanted to point one thing out around cookies. You said
Firstly, you definitely come up with another alternative to
localStorage
later in the article withindexedDB
. However, you absolutely can write cookies from JavaScript. It's as straightforward as:You can then read the document's cookies again with:
Now, all we need to do is flawlessly fix XSS and we won't have to worry about any of this again!
Doesn't this approach have the same security flaw as storing an authorization token in the local storage? I mean, every JS code is going to be able to read them.
Well, yes. As Randall points out above, he was referring to
httpOnly
cookies, which cannot be read or written to from client side JavaScript. I think that paragraph just lacked that context. Client side cookies are just as susceptible to XSS attacks as localStorage. I just didn't agree that just cookies needed a server to write them,httpOnly
cookies do though.Good point -- I was referring to the httpOnly ones. Good catch!
If a rogue script can send the contents of local storage to a server, can't they just add a key logger as well? In other words, if someone can inject malicious JavaScript into your site, you've got problems no matter what you do.
Title of the article could have been "Please Stop Using Local Storage For Sensitive Data" rather than "Please Stop Using Local Storage"
YES!
If it's not sensitive, why would you even store it?
Heyo! I can tell you obviously aren't a fan of the article.
One thing I would say though: just check out the point of the article. I'm not telling people to do anything insane. I'm trying to encourage people to simplify their apps, their code, and often their security strategy by simply using cookies.
They can both contain the exact same info, transmit the same data (in the same way! via http headers), and provide very little differences in terms of implementation/API.
What I'm suggesting here is simpler, quicker, and more secure than what I've described as the problem.
And... I'm not the only one saying this stuff. OWASP, Thomas Ptacek (a well-known security researcher), and many other smart people constantly try to dissuade people from doing the things I mention above. This isn't anything obscure that I'm talking about.
This story creates so much FUD(Fear, Doubt and uncertainty) about a very common tool like Local Storage which is very essential for a lot of web applications.
Careful research on this topic will tell you that there are no known Local Storage vulnerabilities that lets some one else steal your data from the browser's local storage.
JWT is an awesome idea that works well for scaling many services.
Storing JWT tokens in local storage is perfectly alright.
Storing ids in Local storage is perfectly alright unless it is something like social security id. Even if some developer did store social security id on local storage, it is not so easy to steal that information from local storage unless the end user has malicious browser plugins. If the end user has malicious plugins then nothing will help.
There is a place for encrypted cookies, local storage, indexdb and server side session storage. There is no one size fits all approach to all problems. In many many cases, storing temporary data like ids and user preferences on that site are perfectly alright.
These ideas belong to the author and definitely not the employers, but I cannot help it, after reading this, I have a very poor opinion of the employer. I doubt I am the only one who thinks like that.
Hey there,
Thanks for leaving a comment!
I understand what you're getting at here, but I strongly disagree. I'm one of many people actively involved in the security community with this viewpoint.
The creators of the JWT spec itself, for instance, did not design it for the way it is being used in web apps described here. There are TONS of resources written by cryptographers (like DJB), security professionals, etc., all pointing to the massive holes and problems that using JWTs and storing tokens in local storage can cause.
It's not just me.
The biggest problem I see is that there is so much misinformation around advocating using these things. This is a big problem.
This article is just one of many which showcase some of the issues with all the insecure recommendations made amongst the general web development community.
I strongly urge you to read through all the other resources out there which have more to say on this topic. Not only does OWASP recommend against using local storage (which was the point of this article), but there are a PLETOHORA of reasons why you don't want to use JWTs AT ALL! The spec itself has SERIOUS issues and has been widely criticized by the security/cryptography community.
The underlying crypto options themselves have a million issues that cannot be easily resolved, and that's all BESIDES the portion this article talks about, which is just the high level architecture/storage piece.
Best way to refute my argument is to provide some references where JWT was unsafe or local storage was compromised.
Instead you are still continuing the FUD and throwing a bunch of other accomplished people's names and complicated crypto words and "security community" to bring credibility to your arguments.
I think you should talk about this to some other experienced people at your work place. People with serious qualifications disagree with your assessment of the topic at hand.
Here are some good resources to read through:
And, re: when is local storage compromised... Read through OWASP top 10: owasp.org/images/7/72/OWASP_Top_10... (XSS has been one of the top 10 web exploits since 2013 and likely far before).
Did you even read these blogs you linked to?
The first link says "This article does not argue that you should never use JWT". but you are confidently advocating everyone not to use JWT at all.
The other links do not show any vulnerabilities in either local storage or use of JWT tokens. They are talking about specific cases of using bad encryption mechanisms with JWT payload.
If you have XSS in your setup, using local storage is least of your concerns.
The links you posted are related to security. None of them support your argument that local storage and JWT are bad.
Yes -- they do.
The first link describes what is talked about in this post. Using JWTs for web auth is not optimal and causes a lot of issues.
The other links talk specifically about weaknesses in JWTs as a protocol, implementation issues, and real security concerns with the spec.
The bit about OWASP + XSS is the main issue around local storage, since it's something difficult to prevent, even when you're using CSP, which I explained in depth in this article itself.
I'm not certain what more you are looking for =/ The usage of JWTs for web authentication tokens is not ideal, and even for valid use cases of JWTs they are not optimal to use due to security issues with the protocol (as well as many popular implementations) you would be better served using alternative token types which avoid JWT issues.
In short:
Not sure what else I can do to explain :o
You are able to talk around the topic in circles while missing the whole point. You are advocating people not to use local storage and JWT tokens based on nothing credible or nothing concrete. You are misleading a lot of people with your story.
There are perfect legitimate use cases for JWT and Local storage. They are very useful tools.
There are no spec issues with JWT and you can use JWT without encryption.
XSS is not an issue with local storage.
Please stop the FUD.
You do not have to explain anything, If what you say is credible and has any merit, people will make up their mind.
What you are saying is directly contrary to what is outlined in those links I supplied.
I'd love to have a more in depth convo at some point. I'd you're interested in continuing the conversation please hit me up: r@rdegges.com. I'd be happy to talk about spec issues in more depth, or issues with architecture as outlined above.
Really open to whatever! It's a complex topic, and fun to look at.
looking back, this sure seems more like a hit piece from a shill than a legit criticism of jwts
Some comments may only be visible to logged-in visitors. Sign in to view all comments.