IFrame Access Token Renewal

Background

In our previous post we described this blog’s User Authentication Behaviour. Next we will describe iframe based access token renewal  for SPAs. This flow is now problematic, due to recent browser restrictions.

Traditional SPA Token Renewal

The traditional SPA token renewal solution relied on returning only a short lived access token to the browser, and storing the access token in memory as the most secure option.

To refresh the token, the authorization server’s HTTP-only SSO cookie was used, which is shared across all browser tabs. Therefore any browser tab should be able to reliably get a new access token by sending the cookie.

Iframe Token Renewal Requests

In this flow, a hidden iframe is dynamically spun up, and its ‘src‘ is set to  an authorization request URL. An extra query parameter of prompt=none is used, to prevent any end user prompts, since the frame is invisible. This  request should also send the SSO cookie.

If the SSO cookie is not expired, the authorization server will issue a new authorization code. The SPA then sends the code to the token endpoint with an authorization code grant message, to get updated tokens.

Eventually the session cookie will expire, and iframe renewal will return a login_required response. The SPA must process this response specially and redirect the user to sign in again:

Iframe Token Renewal Options

The following settings affect how iframe token renewal works:

Setting Description
Silent Redirect URI The location on which to receive and process the silent token renewal response
Automatic Silent Renew Whether renewal is done using a background timer or on demand
Token Storage If tokens are stored in memory, then every browser tab needs its own access token

This blog’s next code sample uses an SPA that stores access tokens only in memory. The SPA will therefore need to try to renew the access token when any of these conditions occur:

  • API returns a 401 response
  • User reloads the browser page
  • User opens an SPA view in a new browser tab

I avoid renewal on a background timer, since this compares the expires_in value from a token response to the current time. Yet an API can somtimes return a 401 response for other reasons, such as token signing key renewal, or an infrastructure event like a load balancing failover.

Visualizing Token Renewal

To enable visualization of token renewal, the updated SPA enables access token renewal to be tested, by clicking Expire Token then Reload Data:

This is a test mechanism that adds characters to the access token to get the API to return a 401 response. The important behaviour is to ensure that the SPA handles expiry seamlessly, by silently renewing the access token, then retrying the API call.

Reliability Problems

Recent SameSite cookie restrictions mean there are now limits on how the browser will send authorization server SSO cookies. For cross domain iframe requests, cookies are dropped by default in the Safari browser, or in Incognito windows from other browsers. All browsers are expected to adopt the Safari behaviour in the near future:

These cookie restrictions help to prevent unwelcome website tracking of users by advertisers and other parties. Yet they have a side effect that traditional OpenID Connect silent token renewal is no longer reliable.

In addition, some authorization servers may not support the silent renewal flow correctly. The SSO cookie needs to be issued with SameSite=none, yet Cognito uses SameSite=lax. This results in all current browsers refusing to send Cognito cookies from an iframe:

Cognito also does not support the OpenID Connect prompt=none parameter. Instead it returns an HTML response to render the login page when the cookie is not sent, instead of the expected login_required error:

If this occurs, the user experiences a hang for 10 or so seconds, after which an SPA might need to present an error display such as this:

Renewal Using Refresh Tokens

An alternative option is for an SPA to use a refresh token to silently renew access tokens. To provide a basic working solution, this approach is used by this blog’s next SPA, when Cognito is used as the authorization server.

Note however that there is no client credential in the refresh token grant request. Therefore, if there is an XSS vulnerability that leaks the refresh token, it could be exploited for a long time by an attacker. This remains true even if one-time-use refresh tokens are used.

SPA Token Renewal Options

There are three options for SPA token renewal in 2021. We have seen that the first option is not reliable, and using the second option is considered an insecure practice.

Method Access Token Renewal Behaviour
Hidden Iframes Medium security using iframe redirects, though this has reliability problems
Refresh Tokens Low security by storing  a refresh token in local storage and using it to renew access tokens across all browser tabs
Same Site Cookies Best security using HTTP-only cookies that contain or reference OAuth tokens, across all browser tabs

Backend for Frontend

In 2021 the preferred option is to use the third option, via a Backend for Frontend. This has best protection against the JavaScript attacks described in the OAuth for Browser Based Apps document.

The SPA can then perform token renewal securely as well as reliably. Once a BFF is in place, you may as well also keep access tokens out of the browser. This is the approach taken by this blog’s Final SPA.

Where Are We?

We have described the traditional SPA token renewal flow, and why it is no longer recommended. However, the second code sample shows how to implement iframe based token renewal, since knowledge of the cookie  and browser security behaviours is useful for developers to know.

Next Steps

  • We will discuss Logout, to complete our SPA Session Management
  • For a list of all blog posts see the Index Page