Secure Single Page Applications by Preventing Token Theft
Ever since Web 2.0 emerged, the path for websites from pages to applications has been set. Today, it’s more common than not to build websites as single page applications (SPAs), rendering the content in the browser using JavaScript directly.
In contrast to classic websites, where the content is rendered on the server side and the server calls the APIs needed to get the data, the single page application must fetch the data from the API directly from the browser.
Single Page Application Security Issues
From an identity security perspective, this has been a challenge for many years. The main problem is that when using OAuth, which is the only security standard for API access in question, the application needs to pass the access token in the request to the API. In a classic website, this is done by the backend, so the access token is never exposed to the browser. However, in the SPA, this must be done in the browser.
The Challenge with Access Tokens
To understand why this is an issue, we need to talk about the token itself. Access tokens in OAuth are commonly Bearer tokens. This means that the party that sends the token does not need to prove that the token belongs to that party. Anyone who gets hold of that token can use it. Usually, this is acceptable as the token travels from backend to backend over TLS. However, with single page applications, this is not the case.
Using Single Sign-On
Several different approaches have been taken over the years to mitigate the risk. One is to have the lifetime of the access token fairly short and require a new login (often using single sign-on — SSO — in a hidden iframe) to obtain more tokens. This, however, is harder and harder to achieve because of the third-party cookie deprecation. It means that iframes to other domains don’t allow cookies to flow, so SSO doesn’t work in that scenario.
Refresh Token Rotation
This issue has led to the use of Refresh tokens in the browser. These are meant for confidential backends, and should be protected by client authentication. Since that is not possible in the browser, the only mitigation is to use refresh token rotation. This means making sure a refresh token can only be used once, and if the same refresh token is used twice, all tokens are invalidated, thus surfacing the fact that theft has been going on.
Debugging Security Vulnerabilities
Now, all of the measures above may look good on the surface, but in practice, they provide no real safeguards against token theft. As Dr. Philippe de Ryck points out in some of his many excellent videos, if an attacker manages to get a cross-site scripting attack live on your site, all bets are off. The attacker can circumvent most measures taken to protect the tokens.
With all of this in mind, the solution is obvious: don’t put tokens in the browser where the JavaScript code can access them. The browser has mechanisms for letting the server set content that the scripts cannot access. The name of the mechanism is cookies.
The OAuth working group in the IETF has been discussing this issue and working on a best practices document to guide OAuth users to do the right thing. The pattern they recommend is the backend for frontend (BFF) pattern. It involves creating a minimal generic backend that can be reused between frontends and adds the confidentiality layer required for single page applications to become secure.
Introducing the Token Handler Pattern
At Curity, we have further developed this approach. We have evolved this into a real architecture, which we call the Token Handler pattern, and we have implemented this as a first-class feature in the Curity Identity Server. The Token Handler encrypts tokens in cookies, disallowing any application code from ever seeing those, and abstracts the OAuth server interaction away from the application. The tokens can later be decrypted by the API proxy before being sent to the API.
This means that no other applications or backends are needed except for the Curity Identity Server and the API proxy, just like in any other OAuth flow.
An important detail is that it’s also possible to set up the Token Handler part of the server to run against other authorization servers, providing a generic solution to the SPA security problem. The solution runs completely without needing a persistence layer, as everything is encrypted and stored in the user’s browser. It’s even possible to use financial-grade extensions such as PAR and JARM, which provide strong security OAuth solutions even for single page applications.
It’s time to level up security for single page applications and remove the token theft problem once and for all.
Further Reading:
Token Handler Deployment Patterns
Token Handler Development Setup
SPA - Single Page Application Security