此前笔者搜索过不少前后端分离，身份验证 token 应该存放在哪的问题，不少结果都说放到 Local Storage，但放到 Local Storage 真的安全吗？这正是分享本篇文章的原因，希望能帮助到其他有相同疑惑的开发者。
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.
Source: Please Stop Using Local Storage
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!
1 // You can store data in local storage using either syntax
2 localStorage.userName = "rdegges";
3 localStorage.setItem("favoriteColor", "black");
5 // Once data is in localStorage, it'll stay there forever until it is
6 // explicitly removed
7 alert(localStorage.userName + " really likes the color " + localStorage.favoriteColor + ".");
9 // Removing data from local storage is also pretty easy. Uncomment the lines
10 // below to destroy the entries
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.
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.
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.
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:
- 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.
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 bootcamps 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!
If you need to store sensitive data, you should always use a server-side session. Sensitive data includes:
- User IDs
- Session IDs
- 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
httpOnlycookie 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=strictcookie flag (to prevent CSRF attacks), as well as the
secure=trueflag (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!
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.
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 =)
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.