Sitecore leaning toward a composable architecture with a mostly headless development model, where all the business logic is shifted to a head app, which also becomes a major point of integration, the majority of which requires some sort of authentication.
TL;DR Do not work with an authentication token on a client! But what if I do have a need for it? Then, read the rest of this blog post to understand what your risks and options are.
In the article, we will look at the reasons for the need to work with the token on the web application client and find out what suits better for storing the token: localStorage, sessionStorage, or cookie without the HttpOnly
flag (the answer: none of these!). We also take a look at the measures to be used for reducing the risk of token leaks through various vulnerabilities.
Introduction
The ways of doing authentication and authorization correctly are one of popular topics in web development. There are various good, bad, and evil practices I still encounter in everyday life while poking some websites with dev tools. With this article, we are going to talk them through. I want to pay attention to the problem, explain its significance, and also advise the approaches for improving security when working with tokens on the client.
The Problem
Faulty authentication implementation while working with tokens on a client increases vulnerability. The common scenario: having an access token for some resources exposed on the client side, followed by XSS attack gives thefts the same access to the same data as the legitimate scripts would have.
One would say:
…we’re working with
%framework%
or%library%
where all issues should be accounted for. Why would I ever bother at all?
Sounds reasonable, doesn’t it? However, the reality is not that straightforward, and in most cases, it is almost impossible to guarantee safety. There might be situations where security threats originate from completely different places. Imagine, placing the token into a wildcard non-HttpOnly-cookie and securing the application from XSS to the absolute, still does not prevent a breach, just because one of the subdomains holds a vulnerable project which neglects all these safety measures.
But why is a stolen access token dangerous? With it, attackers will be able to make requests to API resources on our behalf or simply impersonate our account in their browser, getting the same level of access.
Evil Case #1: Access token in localStorage
The application receives an access token from the backend and saves it into the localStorage. In this case, if there is an XSS vulnerability, the token can be obtained by an attacker. Let’s say, the token has a lifetime of 15 minutes. Is there a risk of attackers using the token within less than 15 minutes? Absolutely! In my opinion, the risk is too big for this approach to be considered.
Evil Case #2: A pair of access and refresh tokens in localStorage
An advanced case of the previous example. An access token has the same lifetime, but there’s also a long-live refresh token – a string used to get a new refreshed access token. While an access token is used to access a protected resource, a refresh token allows you to apply for a new (or additional) access token. As in the above example, an XSS attacker can gain access to both an access token as well as a very long-lived refresh token.
There is a set of measures to improve the security of refresh tokens: token rotation, protection against re-use, proper (reasonably balanced) selection of lifetime, and others. However, these measures should complement and add on rather than replace XSS token leakage protection.
Evil Case #3: Access token within a wildcard non-HttpOnly cookie
I have come across this approach several times and it carries the greatest risks of all those above. I believe, the intention of using a wildcard cookie is supposed to be for “seamless” authentication between subdomains, but I would like to warn against the careless usage of such an approach.
The catch is that the token placed into a cookie becomes available for all the subdomains of a given site. Even if an attacker fails to find an XSS hole in the main application, a vulnerable service might exist among the numerous subdomains. Actually, scanning subdomains is a typical technique conducted in pen testing. subdomains may contain a lot of filthy things: a legacy site, test project, sandbox/playground, unprotected APIs, or any similar potentially vulnerable service. In addition to that – there might be also a subdomain takeover attack which also empowers an attacker to steal the token in a similar way.
Why at all then one needs to work with a token on a client?
The logical question coming from the above is: why at all do we need to make the token available on a client and work with it there? Why not just put it in a session-hardened cookie? Other than poor application design, I could name a few valid reasons.
1. Using stateless tokens
Unlike “stateful” tokens that serve as a key to session-based data kept at a server, this token is self-sufficient and contains all the required information for authorization. Typically, JWT tokens are used for that purpose, and they have a standard structure:
Due to the JWT signature block, the validity could be verified upon a receipt of request having such a token (either a private key for symmetric encryption or a public key for asymmetric encryption). That means JWT validation takes place without the issuing server (ie. no need to reach a database for each validation).
The approach allows is suitable to stay away from storing tokens at a server by issuing such a JWT token upon the authentication and keeping it at a client.
2. Using OAuth 2.0/OIDC for authentication in your own application
A fairly popular thing is to use, for example, the Authorization code grant flow in OAuth2.0 or OpenID Connect (OIDC).
Some confuse OpenID Connect with OAuth 2.0 – in fact, OIDC extends OAuth 2.0 and is an authentication protocol, while OAuth 2.0 is natively an authorization protocol. However, you can often see implementations of OIDC on top of OAuth 2.0. The difference is that in the case of the OIDC Authorization Server, the Resource server also plays a role, but only for the user’s identity.
3. Using SPA without a backend
Without relying upon a backend, developers are forced to look for ways to work with the token on the client. Disputes on the Internet used to be popular where it is better to store a token to work with it: in localStorage, sessionStorage or cookies (of course, without HttpOnly
). In fact, in terms of security, there is practically no difference. And that’s why: