'Why is auth0 recommending not to store tokens in localStorage?

Auth0 provide extensive list of resources describing best practices for the authentication. Among them there's a constant stream of advice not to use localStorage as a mean to store (JWT) tokens.

I've found several issues with those points:

  • There's strong assumption that JWT token is the sensitive information that mustn't be accesses by the attacker (via XSS)
    • From my perspective accessing the tokens itself doesn't extend the scope of attack. If the attacker has control over the victim's browser they can execute the calls, using the token, from the affected browser itself.
    • Access tokens usually have fairly short expiration time and they can be browser-pinned, reducing the opportunity to use them outside of XSS context.
  • Auth0 recommends using auth0.getTokenSilently() from their SDK to obtain the token, but as far as I see, there shouldn't be any reason why attacker couldn't call this method themselves (i.e. injecting another sdk script, using the existing client keys, and just calling the getToken from there), thus obtaining the token in pretty much the same manner as if it was stored in localStorage
  • The only way that I know where XSS wouldn't be able to access the tokens is basically using httpOnly cookies, but that creates new vectors by itself (CSRF) and still wouldn't prevent attackers from calling the api from within the affected browser.

So I fully agree with OWASP recommendation, not to store sensitive data in localStorage, I would never think of storing credit card numbers or passwords or even personal data there. But that's only because those things would allow attacker to expand the scope of their attack (access the bank account, try to reuse user's passwords in other apps, etc.). But I struggle to see how are accessTokens affected by this.



Solution 1:[1]

Although I don't know much about Auth0 implementations, features and design decisions, from my general understanding of OAuth2 and security, I can try connecting the dots.


A single recommendation by itself doesn't provide enough security or desired functionality, but when used with a combination of other related recommendations, we can achieve the desired level of security and behavior.

Let's go through the points you raised.

From my perspective accessing the tokens itself doesn't extend the scope of attack. If the attacker has control over the victim's browser they can execute the calls, using the token, from the affected browser itself

The problem with localstorage is:

  1. localStorage and sessionStorage are not shared across sub-domains. This is show stopper for SSO functionality (There are some solutions using iframe, but those look more like workarounds rather than a good solution. And when the response header X-Frame-Options is used to avoid clickjacking attacks with iframe, any solution with iframe is out of question)

  2. XSS can send the access and/or refresh tokens to remote servers controlled by the attacker and thus allowing the attacker to impersonate the user

Note: The vulnerability mentioned in point 2 can be mitigated by using a Sender Constrained Access Tokens approach where the client has to prove that they indeed own the token. Another alternative is the fingerprint approach mentioned in OWASP which needs a cookie. However, it seems Auth0 doesn't implement any of these. Therefore, the recommendation of avoiding localstorage makes sense.

Auth0 recommends using auth0.getTokenSilently() from their SDK to obtain the token, but as far as I see, there shouldn't be any reason why attacker couldn't call this method themselves

Correct. That's why

  1. we need to mitigate the risks of XSS by following OWASP XSS prevention guidelines in the first place.
  2. Also, the getTokenSilently() method requires you to have Allow Skipping User Consent enabled in your API Settings in the Dashboard. Although I don't see a specific guideline on this, but I think if you store the token in cookies you don't need this option to be enabled and thereby eliminating any possibility of misuse of the method.

The only way that I know where XSS wouldn't be able to access the tokens is basically using httpOnly cookies, but that creates new vectors by itself (CSRF) and still wouldn't prevent attackers from calling the api from within the affected browser

True. But you can mitigate this with one or a combination of the following approaches:

  1. Use a SameSite cookie. Refer the doc here. If the browser doesn't support SameSite cookie, follow another approach from below
  2. State Variable (Auth0 uses it) - The client will generate and pass with every request a cryptographically strong random nonce which the server will echo back along with its response allowing the client to validate the nonce. It's explained in Auth0 doc.
  3. Use a CSRF cookie with a cryptographically strong random value such that every AJAX request reads the cookie value and add the cookie value in a custom HTTP header (except GET and HEAD requests which are not supposed to do any state modifications). Since CSRF cannot read anything due to same origin policy and it is based on exploiting the unsafe HTTP methods like POST, PUT and DELETE, this CSRF cookie will mitigate the CSRF risk. This approach of using CSRF cookie is used by all modern SPA frameworks. The Angular approach is mentioned here
  4. Always check the referer header and accept requests only when referer is a trusted domain. If referer header is absent or a non-whitelisted domain, simply reject the request. When using SSL/TLS referrer is usually present. Landing pages (that is mostly informational and not containing login form or any secured content may be little relaxed ?and allow requests with missing referer header
  5. TRACE HTTP method should be blocked in the server as this can be used to read the httpOnly cookie
  6. Also, set the header Strict-Transport-Security: max-age=<expire-time>; includeSubDomains? to allow only secured connections to prevent any man-in-the-middle overwrite the CSRF cookies from a sub-domain

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shiva