Curity token handler design overview

Token Handler Design Overview

On this page

Browser Security

In recent years it has been common to implement OpenID Connect for SPAs solely in JavaScript. This is no longer recommended, for two main reasons:

  • Using access tokens in the browser has more security risks than using secure cookies, so SPAs should instead use cookies with recent SameSite improvements.
  • Recent browser changes to prevent tracking may result in dropped third party SSO cookies during traditional SPA iframe-based flows, leading to blocking usability issues.

The Token Handler Pattern article provides background on threats in various web architectures, resulting in a current SPA Best Practice of using a Backend for Frontend (BFF) approach. The SPA then implements OpenID Connect security via a backend component that manages token storage and issuing of secure cookies for the browser.

Token Handler Pattern

The token handler pattern therefore issues only the most secure HTTP-Only, SameSite=strict cookies on behalf of the SPA. These are first-party cookies and not subject to browser restrictions:

Token Handler Pattern issuing first-party cookies on behalf of the Single Page Application

There are key differences between the token handler pattern and alternative approaches:

  • The SPA's Backend for Frontend is separated from the Web Static Content Host.
  • The SPA makes secured API requests to a high-performance API gateway.
  • The SPA controls its own OpenID Connect flow using API requests.

These design choices maintain a pure SPA architecture, where developers can focus solely on the frontend customer experience, and authorization responsibilities are always implemented on the API side of the architecture.

Business Benefits

The main benefits of using the token handler pattern are summarized in the below table:

GoalDetails
Best Browser SecurityFollows current best security practices for browser based apps, with no tokens in the browser and only SameSite=strict cookies used to call APIs
Great User ExperienceAll UI actions, including OpenID Connect redirects, are initiated in the browser so that the app has best control over usability
Modern Developer ExperienceThe UI is developed solely in a client based technology stack such as React, without the need to use cookies to call a web back end
Simple SPA CodeThe OpenID Connect and API code in the SPA is mostly one liners, with less code impact than JavaScript or web back end security libraries
Deploy AnywhereThe SPA is built to static content that does not need securing and could be deployed to a Content Delivery Network (CDN)

Hosting Prerequisite

For any web application that uses secure cookies, the domains used need to be designed with care, and this is also true when using the token handler pattern. For a web origin of https://example.com or https://www.example.com, the token handler components must either run on the same domain, or a related domain such as https://api.example.com or https://bff.example.com.

Token Handler Hosting

For secure cookies to work reliably for the SPA they must be first-party, and the token handler components must be hosted on the same parent domain as the SPA's web origin

OAuth Agent

When the SPA needs to initiate a login or perform other OAuth work it makes Ajax calls to the OAuth Agent. This is a utility API that acts as a Backend for Frontend for the SPA. The OAuth Agent makes OAuth requests to the authorization server and attaches a client credential when needed. After user authentication, the OAuth Agent writes secure cookies that are returned to the browser.

OAuth Agent Operations

The following operations represent the OAuth Agent's API interface, and all of these are designed to serve SPAs via Ajax requests from the browser:

EndpointDescription
GET /sessionGet the user's authentication state and claims from the ID token.
POST /login/startStart a login by providing the OAuth authorization request URL to the SPA and setting temporary cookies.
POST /login/endEnd a login and return HTTP only encrypted cookies to the browser, with claims from the ID token.
POST /refreshRefresh the current access token and rewrite secure cookies.
POST /logoutClear secure cookies and return an end session request URL.

OAuth Agent Cookies

The OAuth Agent issues an encrypted proxy cookie that transports access tokens to APIs. It also issues an encrypted session cookie that contains the refresh token and the ID token. Finally, a temporary cookie is used between /login/start and /login/end containing the OAuth state and PKCE code_verifier parameters. All cookies have the following properties and are issued to the token handler's exact domain:

PropertyDescription
HTTP-onlyThe cookie cannot be read by JavaScript code
SecureThe cookie requires an SSL connection
SameSite=strictThe cookie can only be sent from the web origin, and not from malicious domains
Path=/Cookies are available to all APIs within the host domain

The OAuth Agent always encrypts tokens into cookies, so that, for example, a user cannot extract the underlying tokens using browser tools. This forces the SPA to always use its backend for frontend as the entry point for API requests, which enforces any web-specific security checks.

Cookie Limits

The OAuth Agent should ensure that cookies do not exceed maximum sizes that result in technical errors when the SPA sends them. Cookies must not exceed browser limits (of 4KB per cookie) or the default maximum HTTP header size (often 4KB) allowed by the API gateway. The largest access tokens are usually RS256 JWTs, in which case the total cookie size can be reduced to around 3KB, with average-sized token payloads.

SPA Cookie Size

You can reduce cookie size further by using opaque access and refresh tokens, or by using a more efficient JWT algorithm, such as ES256 or EdDSA.

Stateless Solution

By storing underlying tokens in cookies, the OAuth Agent acts as a stateless API, that receives all state in the cookies of incoming requests. You do not need to manage any backend storage. You can also cluster instances of the OAuth Agent and any instance can process any OAuth request.

Login User Experience

The OAuth Agent never abruptly redirects the SPA, and instead returns OpenID Connect URLs to the SPA, so that the location and page state can be saved before a redirect and then restored after. In addition there are no dropped cookie problems during navigation, since cookies are not needed during web navigation requests. All of this leads to the type of behavior you would expect from an SPA, where the frontend code is in control of the UX.

OAuth Proxy

Once user authentication has completed, the SPA makes API requests to access data. These requests are usually cross-origin, since web static content hosting is best managed independently to API hosting. For secure and reliable end-to-end SPA to API requests, you must take into account these cookie based concerns:

This work just involves general security plumbing, which is best kept out of your API code. Instead, since APIs are already hosted behind an API gateway as a security best practice, the gateway is the simplest place to deal with them. This is done using plugins, which perform well, since they do not require the gateway to make any additional remote calls. These plugins deliver a JWT access token to your microservices.

An example end-to-end API flow is illustrated below, where an OAuth Proxy plugin decrypts a cookie and updates the HTTP authorization header with the access token. If opaque access tokens are used, a Phantom Token Plugin can also run, to read the opaque access token from the authorization header, introspect it and then update the authorization header with a JWT.

Illustration of an end-to-end API Flow

Web Architecture Styles

In the token handler pattern, your web host is always lightweight and never needs to deal with the intricacies of cookie security. There are a few different web architecture styles you should be aware of.

Unsecured Apps

Data in unsecured apps like blogs or news sites can be pre-rendered to static content before it is deployed. Technology stacks such as NEXT.js enable this, and you can take full advantage of the technology to deliver a high-performance site with good Search Engine Optimization (SEO).

Yet an OAuth secured web application has different characteristics. The data to display is managed by APIs, requires secure access, varies by user and is not reachable by search engines. You therefore need a different architecture design.

Secured SPAs

In a pure SPA, the web host only delivers static content. The web host never needs to receive secure cookies, since these are only used on the API side of the architecture. Typically, you design a simple first page with a fast time to first render. Subsequent user navigations result in API requests and fast updates that avoid reloading the entire page.

Various techniques can be used to optimize performance. You can use HTTP cache headers to serve already downloaded static resources from the browser cache. You can use compression and bundle splitting to limit the size of downloads, and a content delivery network to reduce their latency. You can also use libraries that pre-fetch and cache API requests.

When a cookie-secured SPA loads, it cannot know if the user is authenticated and must ask the server for the login status. You can apply optimizations to reduce the frequency of these calls. For example, after login you might store an isLoggedIn=true property in local storage and use that value to avoid OAuth Agent calls when opening new browser tabs.

Secured Hybrid Apps

In some cases your web app may have characteristics of both an SPA and a website. For example, in NEXT.js the web host might deliver a main page that pre-renders some unsecured data to HTML as an alternative way of enabling a fast time to first render. The frontend no longer needs to ask the server if the user is authenticated.

To enable this with the token handler pattern you need to ensure that an OAuth Agent cookie flows to the web host, to inform it that the user is authenticated. You might do so using a Same Domain Deployment. The web host can then check for the presence of the cookie and inform the frontend of the authentication state by returning a particular pre-rendered response.

Secured Websites

The token handler pattern is not meant to provide a website solution, but could potentially be used to migrate an older website solution to an SPA. Traditionally, websites read secured data from a database and pre-render it to HTML before returning it to the browser. This is sometimes called Server Side Rendering (SSR).

Yet pre-rendering secured data from APIs can add complexity to your web host and may be less efficient than direct calls from the browser to APIs. If you want to use SSR for secured API data, the web host may need to decrypt cookies to get access tokens to forward to APIs, and will also need to manage token expiry events. In this case, implementing the OAuth Agent logic in your web host, or using a website stack, is likely to provide a simpler solution than the token handler pattern.

Micro-Frontends

The token handler pattern separates web and API concerns to reduce complexity and give you choices. It is possible to deploy multiple SPAs within the same web domain, with seamless navigation between them due to shared cookies. For example, you could deploy secured and unsecured apps at different paths, or you could split a large web application into micro-frontends to keep your SPA code sizes manageable.

Cookies issued by token handler components are sharable by all secured micro-frontends within a web domain, even if frontends use different technology stacks. Yet we only recommend cookie sharing when all apps are for the same business area. The OAuth and SameSite Cookies article provides further information on scaling the use of cookies across applications.

Developer Experience

The token handler pattern can enable frontend developers to run code for just the Web UI, and to work solely in a browser based technology, such as React or Angular. This provides maximum productivity, with static content built locally and no need to run a complex web backend. Moving to the token handler pattern does require developers to manage additional components though. See the Web Development Setup article for some recommendations on managing this.

Due to the use of the token handler pattern, the SPA's OpenID Connect and API calls follow current best practices for browser based apps, but of course this does not make your app fully secure. Software companies must also follow a security development lifecycle, and in particular protect against Cross Site Scripting (XSS). If there is an XSS vulnerability then secure cookies could be abused by an attacker to steal data.

Professional Implementations

At Curity, we provide expert implementations of the OAuth Agent and OAuth Proxy, which will work with any standards-based authorization server. This enables companies to simply configure or deploy a working solution, without needing to build their own cookie integrations from scratch. For more information about Curity's offering, see the following resources:

Conclusion

Using cookies optimally in an SPA is challenging, but needs to be done in order for SPAs to be secure. The best current way to manage this is to use the token handler pattern, which improves security without losing any of the benefits of an SPA architecture.

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