Token Handler Overview

Token Handler Overview

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 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 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

The key difference between the token handler pattern and alternative approaches is that an API driven OpenID Connect flow is used. This is initiated by Ajax requests from the SPA, and secure cookies are then issued by utility API components, rather than by a web backend. This avoids impacting the overall web architecture and maintains all of the other benefits of SPAs.

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 run on a related domain such as https://api.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 sign the user or perform other OAuth work it makes Ajax calls to the OAuth Agent. This is a utility API which makes OAuth requests to the Identity Server and attaches a client secret when needed. After user authentication, the OAuth Agent writes secure cookies that are returned to the browser.

In total the following non-persistent cookies are issued, if a cookie prefix of example is used, each of which is strongly protected using AES256 symmetric encryption, with the encryption key known only by token handler components:

Cookie NameContains
example-loginA temporary cookie used between start / end login, to verify that the state before and after login matches, and to store the PKCE code verifier
example-authThe main SPA cookie contains the refresh token representing the user's authenticated session
example-idThis cookie contains the ID token, the contents of which the token handler API provides to the SPA via its /userInfo endpoint.
example-atThis cookie contains the current access token, which is used later when calling APIs
example-csrfThis cookie can be used for additional protection, as described in OWASP CSRF defense in depth

All cookies have the following properties, though the paths of the refresh and ID cookies are restricted to the OAuth Agent endpoints that use them. Cookies only need to be issued to the API domain, and are not used during requests for web static content, or during user navigation:

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
Domain=api.example.comThe cookie is only needed when calling APIs
Path=/The access token cookie is available to all APIs within the API domain

If following Curity token recommendations, you will be using the Phantom Token Pattern, where access and refresh tokens issued are small opaque values, similar to UUIDs. This means cookies issued do not exceed browser or HTTP server limits. Cookie issuing is then stateless from a backend viewpoint, without the need to implement any token storage.

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 front end code is in control of the UX.

API Requests

Once authentication has completed, the SPA will make API calls to access data. These requests are usually cross-origin, since web static content hosting is usually done differently to API hosting. The end result for your microservices should be to receive a JWT access token, and the preferred end-to-end API flow is illustrated below:

API Flow

OAuth Proxy

In order for end-to-end SPA to API requests to be made securely and reliably, it is then necessary to deal with 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 a reverse proxy / API gateway as a security best practice, the gateway is the simplest place to deal with them.

The OAuth Proxy is a small component that implements these tasks, and is deployed as a lightweight plugin component, so that the code involved performs well under load.

Tested Implementations

At Curity, we are providing tested implementations of the OAuth Agent and OAuth Proxy, which will work with any Authorization Server that supports the security standards used. This enables companies to simply configure or deploy a working solution, without needing to build their own solutions from scratch. For a list of all token handler resources, including guidance on integration and deployment, see the OAuth for Web Home Page.

Developer Experience

Using the token handler pattern enables 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 web back end. Moving to the token handler pattern will require some initial work to ensure a productive local setup though. See the Deployment Patterns article for a proposed setup.

Remaining Browser Threats

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.

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 maintains all of the other benefits of an SPA architecture. To get started and run an SPA that uses the token handler pattern, see the SPA Code Example.