Why JSON web tokens shouldn’t be stored in local storage
I’ve always wondered: why are we still using cookies when we can use JSON Web Tokens (JWT)? They offer so many benefits like being stateless, self-storing, and so on.
After digging and asking around, I found out that local storage was never meant to be used as a secure storage. Bear with me here.
I came across many tutorials that tell you to put JWT in local storage — in fact all articles on JWT that I came across said this. But whatever the reason they advise it, this practice needs to stop.
I have seen many sites where sensitive information was stored in local storage. And it bothers me to know that so many developers are opening themselves up to devastating security issues by doing so.
First let’s understand what is local storage?
// Either this // Considering it as a object localStorage.userName = "highskillzz"; //or this way! localStorage.setItem("objects", "0"); // Once data is in localStorage, it'll stay there forever until it // is removed explicitly console.log(localStorage.userName + " has " + localStorage.objects + " number of objects."); // Removing data from local storage is also pretty easy. Uncomment // below lines //localStorage.removeItem("userName"); //localStorage.removeItem("objects");
What’s Awesome About Local Storage?
Although this article is about why not to use Local Storage, there are many cool things about it too. Like….
Also, the other great thing about local storage is the size restriction: we can use it to cache data for later usage. But in the case of cookies, this isn’t the case as there is a size restriction of 4KB, while Local storage provides at least 5MB of data storage across all major web browsers.
Then what’s so bad about Local Storage?
I feel like many developers might not realize just how basic local storage actually is:
It can only store string data. This makes it pretty useless for storing data that’s even slightly more complex than a simple string.
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.
In a nutshell, the only situation in which you should use local storage is 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.
Why Local Storage is Insecure and You Shouldn’t Use it to Store Sensitive Data
Although even if you consider that a slower app and a little annoyance while developing is fine, Security is the top most priority and cannot be cast aside. 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.
Storing in local storage any sensitive information is equivalent to posting on facebook or instagram that information. Yeah! it’s pretty bad. 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 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:
- 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
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.
You might consider not at all using third partly libraries but as we all know that is not at all possible if you want a good modern site working.
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.
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 a lot 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 is not entirely correct and has a lot of flaws with it.
What is the alternative to Local Storage then???
Well there are two possible alternatives:-
- Instead of storing the JWT in local storage, store it in a cookie(I don’t recommend this. Read on to find out why)
- The other is to use server side authentication by using sessions and cookies(Recommended)
There are alternatives to using JWT altogether:-
Read more about this here - https://kev.inburke.com/kevin/things-to-use-instead-of-jwt/
Although this article is for showing that JWT in local storage is not good, there are reasons why you shouldn’t be using JWT altogether, that can be read more about here:-
The second option is recommended and tutorials can be found almost anywhere so I won’t be explaining why but instead why not the first option.
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.
This is correct but 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.
Finally, 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.
Thank you for reading. I mostly write tutorials on the problems I have faced and whatever new tech I explore, so stay tuned for the next article.
If you found this article helpful, don't forget to subscribe for more 👏.
A lot of people mistakenly try to compare “cookies vs. JWT”. This comparison makes no sense at all, and it’s comparing apples to oranges — cookies are a storage mechanism, whereas JWT tokens are cryptographically signed tokens.
They aren’t opposites — rather, they can be used either together or independently. The correct comparisons are “sessions vs. JWT” and “cookies vs. Local Storage”.