Secure Browser Storage
4 min read

Secure Browser Storage

When talking about browser storage and security, the top 1 concern is an XSS vulnerability. Having a solid understanding of what the browser storage options are and what security guarantees they offer can help developers make an informed decision on how to build an as-secure-as-possible browser storage solution.

This blog is a summary of my article from the Auth0 blog.

Browser Storage Options

The browser storage options are:

  1. Local Storage
  2. Session Storage
  3. Cookies
  4. In memory

Local Storage and Session Storage isolation is based on Same Origin Policy. Same Origin Policy controls access to data between websites. It restricts a document or script loaded from one origin to interact with a resource from another origin.

Local Storage is not an effective security protection against XSS attacks, as the JavaScript that an attacker injects via XSS runs on the same origin as the rest of the browser application code.

Session Storage has some additional protections. Data stored in Session Storage is cleared when a browser session ends and is not shared between two different browser tabs or iframes. Although this has some security benefits because of reducing the attack surface - reflected XSS attacks are not possible - it is still not considered a secure browser storage option as other types of XSS attacks can retrieve data stored in Session Storage.

Cookies offer protection against XSS only when the HttpOnly flag is set. However, an XSS+CSRF attack can be used where the attacker injects code that performs a request to the backend. The browser will include the HttpOnly cookie to the request allowing the attacker to retrieve the response data. At the same time the HttpOnly cookie mandates that not even the legitimate frontend code can have access to the cookie value.

In-memory storage can take different forms. Most of them are still vulnerable to XSS attacks if the secret value is assigned to a top level variable at some point. One in-memory storage option that offers a level of security protection are JavaScript closures as they emulate private methods. However, closures are still vulnerable to function override attacks: An attacker can override the functionality of a function that is externally defined but is used within the closure to process the secret (e.g window.fetch). To mitigate this threat, a private reference of all externally defined functions would need to be kept within the closure. While this is feasible  - level of security protection are JavaScript closures as they emulate private methods. However, closures are still vulnerable to function override attacks: An attacker can override the functionality of a function that is externally defined but is used within the closure to process the secret (e.g window.fetch). To mitigate this threat, a private reference of all externally defined functions would need to be kept within the closure. While this is feasible  -but cumbersome - for native JS functions, it impossible when using 3rd-party dependencies (which in turn use other dependencies).

Web Workers as a Secure Browser Storage Option

Taking a step back from looking at our options and discussing what security protection they offer, what we are looking for is to write code that handles sensitive data in an untrusted environment. We’ve already looked at origin sandbox: Local Storage, Session Storage, cookies. Our verdict was that this is not a secure option with the exception of HttpOnly cookies. We’ve also touched on JavaScript Closures. Let's now focus on process isolation using Web Workers.

Web Workers run JavaScript code in a background thread separate from the main execution thread of the JS frontend application and communicate with the frontend application via a channel called MessageChannel.

A fundamental requirement for storing a secret within the memory of a Web Worker is that any code that requires the secret must exist within the Web Worker. If the Web Worker passes the secret to the rest of the browser application at any time, an attacker can override the MessageChannel object and retrieve the secret when it's transferred.

Same as with HttpOnly cookies, storing secrets within the memory of a Web Worker also protects the confidentiality of the secret. An attacker cannot obtain the value of the secret but they can use it indirectly by sending messages to the Web Worker to perform an operation that requires the secret, and returning back to them the result.

The advantage of a Web Worker implementation compared to an HttpOnly cookie lies in the fact that with a Web Worker implementation  the secret is available in the isolated JavaScript code of the Web Worker. In case of an HttpOnly cookie, no JS code can access the value of the secret. If access to the secret is needed by a part of the frontend application, the Web Worker implementation is the only one satisfying the requirement while preserving the secret confidentiality.

Browser Storage: Security Options Recap

If a secret cannot be kept in isolation, any browser storage is susceptible to XSS. The different storage options that provide some level of effective isolation are HttpOnly cookies, simple cases of in-memory storage within private closures with local copies of externally defined functions and Web Workers.

None of the options are bullet proof from a security perspective; still, they protect the confidentiality of the secret. Finding the right solution depends on your application requirements but always consider moving away from a browser storage design.

Where to Learn More

Learn more about secure browser storage and dive into the details of the topics presented here in my article https://auth0.com/blog/secure-browser-storage-the-facts/ from the Auth0 blog or in my OWASP AppSecIL 2020 talk "Security Facts and Fallacies around Browser Storage": https://youtu.be/roG99LkSkZA

Browser Storage Security Playground

Play around with sample applications showcasing browser storage security nuances in: https://github.com/esarafianou/browser-storage. They show the differences between Local and Session Storage and demonstrate different in-memory storage implementations and their insecurities against XSS. Instructions and details can be found in my article from the Auth0 blog.