Understand how to use the token handler patter for single page applications to improve user experience and strengthen security.

The Token Handler Pattern for Single Page Applications

On this page

The Evolution of Web Applications

The web is constantly evolving. From content to technologies and architectures, modern web applications continue to change. The monolithic website (a single backend serving HTML and data to user interfaces) was once a prevalent style. Lately, this monolithic approach has been replaced by microservices on the backends and Single Page Applications (SPAs) on the frontend. This change has caused a significant shift in the way security is handled.

In a traditional website approach, a user's access to a web application was controlled using a cookie-based session.

Cookie-based session in a website

In this setup, the frontend client uses cookies to authorize a user during calls to the backend. The cookie is stored by the browser and sent to the backend with every request so that authorization decisions can be performed based on the session data kept in storage. With this approach, it doesn't matter whether the application is a monolith or the backend is driven by microservices.

In contrast, an SPA has no dedicated backend at all. The SPA code itself is often served by a Content Delivery Network (CDN) through static files (JavaScript code, stylesheets, images, etc.). The app calls APIs, which return relevant data. By default, as there is no dedicated backend for the SPA, the user's session cannot be kept in a cookie.

Instead, SPAs can use access tokens to call APIs on behalf of the authenticated user. OAuth2, OpenID Connect, and JWT are popular standards to enable the use of access tokens, though proprietary solutions are also utilized. In a basic implementation, the authorization server uses cookies to secure logins and to enable Single Sign On (SSO). Yet the session between the SPA and APIs uses only access tokens.

SPA calling APIs with access tokens

The Security Issues of an SPA

The shift from handling authorization with cookies to access tokens has some severe security implications. The frontend code, which runs in an insecure environment (the user's browser), requires access tokens to call APIs. Very often, the SPA will also possess a token that grants offline access to a user's resources. This could be a refresh token that can obtain new access tokens without interaction from the user. As these credentials are readable by the Single Page App, they are vulnerable to Cross-Site Scripting attacks (XSS).

Any malicious code that manages to run in the context of the SPA will potentially be able to read the access and refresh tokens (This could be done by loading them directly from browser storage or intercepting them when sent in HTTP requests). The attacker will be able to perform any action the token permits. In the case of accessing APIs, this could potentially give an attacker access to functionalities not normally accessible through the UI, especially when over-privileged tokens are issued to the SPA. The attacker can also send the tokens to an external application and perform actions even when the user closes their application. If the attacker manages to extract a refresh token in this way, they will be able to access the victim's data for as long as that refresh token remains valid (which can be days in some setups).

The above examples show how dangerous an XSS attack is for an SPA. Malicious JavaScript threats are described in detail in the OAuth for Browser Based Apps document. Unfortunately, current browsers offer no way for an SPA to securely store tokens. Developers should thus always make XSS mitigation a priority.

Another looming issue for SPAs is the impending end of support for third-party cookies by the major browsers. This means that some SPA techniques for refreshing access tokens issued by authorization servers can be hampered. Keeping refresh tokens in cookies (as is sometimes implemented by SPAs) can also become a problem should the token service operate on a different domain than the SPA. You can read more about first-party and third-party cookies in Best Practices - OAuth and Same Site Cookies.

Security of Cookie-Based Sessions

Websites that rely on cookies to gain access to protected resources are in some ways better protected from the previously described attacks. Yet, they are not 100% bulletproof. Session identifiers stored in HTTP-only cookies cannot be accessed by any script run in the browser. This means that an XSS attack cannot read a cookie to steal the session identifier and use it straight from the attacker's app to access the user's resources.

On the other hand, such an attack can perform calls to the website's backend, relying on the browser to automatically add a session cookie to such calls. The attacker will still be able to access the user's data, but will be limited to the lifetime of the user's session (no offline access is possible in such a scenario). Websites that rely on cookies are also subject to Cross-Site Request Forgery (CSRF) and need to be additionally protected from this attack vector. SPAs are immune to CSRF, as requests to APIs rely on headers added by scripts, not by credentials appended automatically by the browser.

Modern browsers offer new ways of further protecting applications. It's now possible to limit cookies used by the application only to secure HTTP traffic (thus inaccessible to scripts or insecure traffic). It's also possible to limit requests to those originating from the same domain as the website. This can be achieved by using the SameSite cookie attribute set to strict. This setting can increase application security but may cause UX issues. For example, a user clicking a link to a website from a genuine email will not be recognized as authenticated, as the request is coming from outside the website's origin.

Proper setting of CORS headers in your application can further inhibit CSRF attacks. Setting strict limitations in Content Security Policy headers will block malicious code from sending requests outside your app (e.g., to steal refresh tokens). Unfortunately, users can disable CSP protections in browsers, either explicitly or by installing dubious plugins. Developers should be aware of this when designing application security.

Recommended Solution for SPAs

Currently, SPAs have no means of keeping access and refresh tokens secure from malicious code. Even if developers attempt to protect their apps from XSS attacks (as they should), such an attack can still occur through a vulnerability in a third-party library. The only way to protect tokens from being accessed by any malicious code is to keep them away from the browser.

This can be achieved by adding a backend component called a Backend for Frontend (BFF) to handle tokens and issue secure cookies to the frontend. Yet the way in which backend cookie issuing is implemented can be an important technical design choice, since some options could have adverse effects on your web architecture:

  • The backend code needed to issue cookies may prevent the use of a content delivery network, impacting global performance.
  • Backends can sometimes affect usability, by reducing the control you have have over login redirects, token refresh and session expiry.
  • SPA developer productivity may be impacted if they must run a web backend that issues cookies, then proxy all API calls via the backend.

The Token Handler Pattern is a modern evolution of the Backend for Frontend pattern, used when implementing OAuth and OpenID Connect. In addition to securing the SPA in the recommended way, the token handler pattern separates web and API concerns so that a pure SPA architecture and all of its benefits are maintained.

Token handler components are deployed to the API side of the architecture and therefore do not interfere with your web development or deployment. The SPA developer is also in full control of all usability-related behaviors and can handle redirects, token refresh and session expiry using JSON responses from token handler components.

How the Token Handler Pattern helps to secure SPAs

Using this approach, all communication from the SPA to the authorization server goes via a backend OAuth Agent component, and tokens will not reach the SPA at all. The OAuth Agent acts as a confidential client for the SPA and supplies a client credential (client secret, mutual TLS, etc.) when retrieving tokens from the authorization server. The OAuth Agent then issues HTTP-only session cookies to the SPA. The security level is on par with a website backend.

When the SPA calls APIs, a cookie is used as an API message credential, then exchanged for an access token. This translation is best done using a dedicated OAuth Proxy plugin, hosted in a high-performance API gateway.

Important Token Handler Features

It Is Still an SPA?

The token handler pattern provides an SPA a level of security similar to a regular website, but it still is an SPA. This means that nothing in terms of app deployment must change — it can still be delivered through a CDN. The app does not need a cumbersome backend to process all business features of the app, just a lightweight component capable of dealing with security. The SPA itself will usually need very few changes to start talking to the authorization server through an OAuth Agent.

Stateful vs. Stateless

The implementation of token handler components can either be stateful or stateless. A stateful implementation would have access to some kind of persistence mechanism (e.g., a database) where tokens from the authorization server would be stored. The OAuth Agent would then issue cookies with a session ID and read tokens from the store.

On the other hand, a stateless implementation does not need any additional persistence layer. Tokens are encrypted by the OAuth Agent and placed in session cookies. As those cookies are HTTP-only, they cannot be accessed from any JavaScript code. Even if they were read, the tokens are encrypted, so the encryption key is needed to get the actual token.

A stateless implementation retains the same level of security as a stateful one. Still, it does not require an additional storage component (which has to be synchronized among all instances, which can additionally complicate the solution). In a stateless implementation, though, cookies sent to the browser are substantially larger, as they contain the full encrypted value of tokens.

Cookies can become large when they store JWTs. You can keep cookies under browser limits by issuing each OAuth token to a different cookie and limiting the API endpoints to which you send each cookie. This enables JWTs with large RS256 signatures to stay within browser cookie limits. Ideally though, issue opaque access and refresh tokens to the SPA client, or consider using an algorithm that produces smaller JWT signatures, such as ES256 or EdDSA.

Different Deployment Options

Token handler components can be deployed in different ways. They will often be deployed as any other backend service; instances with the deployed code will be spawned in data centers and exposed through a load balancer or gateway.

Since the token handler pattern is intended to be lightweight, serverless functions are another valid deployment option. Many CDNs offer static content delivery and the ability to run serverless functions on their infrastructure (take Cloudflare workers, for example). This means that the OAuth Agent and OAuth Proxy, in some cases, can be run without the need for any additional infrastructure.

Whatever type of hosting is used, the token handler components must be accessible on the same domain as the SPA, a sibling domain (e.g., www.example.com for the SPA, and api.example.com or bff.example.com for the backend), or a child domain. This enables cookies issued to be first party and prevents them being dropped by browsers. The cookies should also use the SameSite=strict parameter, to maintain a high level of security.

Login Usability

When using the token handler pattern, the SPA receives first-party cookies that work reliably in all modern browsers. The SPA only actually needs to send the cookie in API requests and not during requests for web content. This means that web navigation actions that might drop cookies never impact the end user. In addition, the SPA is in control of when the actual OAuth redirects occur, and can write custom code before and after such redirects (e.g., to save and load application state).

Hiding Complexity of Authorization Flows

The OAuth Agent is responsible for all communication with the authorization server. This hides the complexity of authorization flows from the SPA. A simple API can be exposed to the SPA, and the SPA does not need to be aware of the security details. This allows the OAuth Agent to choose different strategies, such as Pushed Authorization Requests, JWT Authorization Requests, or JWT Security Authorization Response Mode, without any code changes in the SPA.

Scaling Web Security

When developing multiple SPAs, use a deployment design that limits the scope of security exploits. If there is a frontend XSS vulnerability or a backend API vulnerability, it might be exploited to steal data using the session cookie.

In larger setups we recommend designing web domains by business area, such as product1.com and product2.com. Give each SPA independent OAuth scopes and enable users to navigate across apps using Single Sign On when required. Doing so limits the scope of session cookies, to ensure that an exploit in one business area cannot affect other business areas. This requires you to deploy token handler components for each web domain.

In other cases, SPAs for the same business area can be split into multiple apps (or micro-frontends) to keep code sizes manageable. These can use different paths within a web domain. The token handler pattern works well in this type of deployment since the same cookies can be shared across micro-frontends without conflicts.

You can also mix and match web styles. For example, a web domain could contain an unsecured micro-frontend that prerenders static content to achieve the best SEO. The web domain could also contain secured micro-frontends hosted at other paths. All micro-frontends could be deployed together to the same CDN static content host.

Token Handler Implementations

Even though the token handler components are lightweight, they are tricky to implement. This is why we at Curity have created tested token handler components which you can simply deploy, or adapt according to your own preferences. For further information on how the token handler pattern works and how you can deploy it see the following resources. You can run the example SPA in a fully integrated setup on a development computer:

Conclusion

The token handler pattern is a recommended Backend for Frontend architecture solution for implemetning Single Page Application Security. As long as browsers have no way of storing tokens securely, it is better to keep tokens out of the browser altogether. Using this pattern does not mitigate all attack vectors; instead, it switches back to sessions and cookies. These attack vectors are better understood and possess superior security mechanisms. Sessions in cookies also offer better protection of credentials (an attacker can use the session, but they cannot steal it to perform actions offline).

Join our Newsletter

Get the latest on identity management, API Security and authentication straight to your inbox.

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial