Best practices for using OAuth and cookies in browser based apps

Best Practices - OAuth and Same Site Cookies

On this page

When using OAuth and OpenID Connect in a browser based application, the two main options are to develop a website or a single page application (SPA). Either of these can use HTTP-only cookies to convey user identities in HTTPS requests, to secure calls from the frontend to the backend.

Browser based apps send first-party cookies to their own backends, which are in the same site as the web origin. During more advanced navigation, web apps can contact external sites, and send third-party cookies. One such use case is during OAuth flows, when the browser interacts with the authorization server.

Recent same-site cookie restrictions can lead to application problems in some OAuth flows. This article therefore explains how to avoid them, to ensure good security, reliability and usability. Some best practices are also provided, on both web cookie security and other cross-domain navigation use cases.

Same-Site Cookies

Current cookie behaviors are explained in the latest updates to the HTTP state management specification, also known as RFC6265. Servers now issue a SameSite attribute when issuing cookies, to indicate its desired behavior. The newer cookie properties are also understood by all modern browsers (or user agents).

SameSite ValueIntended Behavior
strictUser agents should send the cookie only for same-site requests
laxUser agents should send the cookie for same-site requests and cross-site top level navigations
noneUser agents should send the cookie for both same-site and cross-site requests

User Agent Restrictions

Modern user agents implement the client-side behavior from the specification, but often go beyond it, adding further restrictions. This protects users against unwelcome tracking by external sites. For example, the Safari browser prevents cross-site tracking by default, even for SameSite=none cookies.

Browser Settings

Cross-Site Requests

The RFC6265 specification explains rules for classifying requests from user agents as same-site or cross-site. This includes actions to inspect the web origin, the target domain, and ancestor documents for nested browsing contexts. The end result is that cookies issued by external domains are considered third-party.

Exact behaviors may vary a little between the main browsers. The main use cases affected are iframes that load content from external domains, or Ajax requests sent from the browser to APIs in external domains.

OAuth Cookies

When authenticating a user, your browser based app will run a code flow. The initial request sent in an HTTP redirect will contain parameters similar to these:

bash
123456789
GET http://login.example.com/oauth/v2/oauth-authorize
?client_id=web-client
&redirect_uri=http://www.product.com/
&response_type=code
&scope=openid profile
&code_challenge=WhmRaP18B9z2zkYcIlb4uVcZzjLqcZsaBQJf5akUxsA
&code_challenge_method=S256
&state=CfDJ8Nxa-YhPzjpBilDQz2C...
&nonce=638088910888703720.YzY3...

The authorization server validates the initial request, then issues a temporary HTTP-only cookie to the browser, including the SameSite=none property. This cookie must be re-sent by the browser for all subsequent requests in the authentication workflow, so that these requests are secured correctly.

OAuth Set Cookie

The authorization server then presents multiple screens. Some of these are HTTP POST requests, such as those to submit a user's password. Finally, once the code flow completes, a single sign-on (SSO) session may be created. This is represented by another cookie, which also uses SameSite=none.

OAuth cookies are usually third-party

Cookies issued by the authorization server will be considered third-party cookies by your web applications, unless the authorization server shares the same parent domain as the web origin.

In your own applications, SameSite=none is not the most secure cookie option. It is the standard behavior for authorization servers, which also implement many other techniques for protecting requests.

Browser Window Types

Browser based apps can send code flow requests using any window that supports redirects. Usually the main window is used, but it is also possible to send the request from a popup window or an iframe. These windows have different privacy characteristics, which affects how the user agent sends cookies.

Main windows and popup windows show the user a navigation bar, to indicate the origin the user has been redirected to. For iframes however, the user is not informed which origin is being used, and the window can even be completely hidden. This increases the scope for iframes to be used for malicious purposes.

The trend in modern browsers such as Safari is therefore to only send OAuth SameSite=none cookies for top level redirects, and to drop them for requests sent from iframes, or when issuing Ajax requests.

OAuth cookies may be dropped when sent from cross-domain iframes

If you send OAuth requests from iframes in a browser based app, the user agent may refuse to send OAuth cookies, unless the authorization server shares the same parent domain as the web origin.

Cookie Best Practices

Before discussing OAuth flows that are restricted by same-site cookies, this section summarizes how the backend of a browser based application should issue cookies for its frontend. The cookie issuing can be done by building a website. Alternatively, a backend for frontend (BFF) can perform this task on behalf of a single page application. HTTP-only cookies issued are then shared across all browser tabs.

Using the latest cookie improvements to secure your browser based apps is now recommended. If you instead use OAuth tokens directly in JavaScript, there are extra attack vectors. The introduction to the token handler pattern provides background on protecting against browser threats.

Issue First-Party Cookies

Always issue cookies from a backend domain hosted on the same parent domain as the browser based app's origin. Avoid issuing cookies from an unrelated API domain, e.g. https://api.company.com, to the web origin of the browser based app, e.g. https://www.product.com. This will issue cookies that are considered third party, and user agents are likely to drop them during API requests.

Follow a Secure Web Development Lifecycle

Web development teams must ensure good browser security. A common option is to follow recommendations published by the Open Web Application Security Project (OWASP). Two of the main threats are cross site request forgery (CSRF) and cross site scripting (XSS). When issuing cookies to browser based apps, the SameSite=strict cookie property provides the strongest built-in CSRF protection.

Consider API Driven Cookie Issuing

The OWASP recommendations includes defense in depth measures to protect against CSRF, in addition to using same-site cookies. When developing single page applications, a convenient place to manage these concerns is in an API gateway, in order to keep the security plumbing out of your application code. The token handler design overview describes this approach, with the following end-to-end flow:

The end-to-end flow in the Token Handler Pattern

It is possible to issue cookies from an API domain, e.g. https://api.product.com, in the same parent site as the web origin, e.g. https://www.product.com. The cookies issued then remain same-site and are not impacted by recent browser restrictions. Doing so provides additional options when designing your web deployments, since cookies are only used in API requests. It is therefore possible to design API routes independently to your web domains.

Protect Cookies

Secure cookies returned to the browser must be HTTP-only, so that they cannot be directly accessed by scripts. They must also use the Secure cookie property, and only be accepted by servers in HTTPS connections.

Avoid storing secure values in the browser, such as in local storage. Instead, use server-side capabilities to protect the confidentiality and integrity of cookies issued. This can include the use of a strong symmetric encryption algorithm, such as AES256-GCM, with a key known only to security components in the backend.

Also, ensure that cookies are time limited and no longer accepted after a defined period of time. This is automatically ensured when all expiry is based on underlying OAuth tokens. It is recommended to also use non-persistent cookies, so that cookies are removed when the user agent is closed.

Limit Cookie Sending

Cookies containing identity information ultimately act as credentials for accessing your data, so send them to as few endpoints as possible, and take care in server code, when setting domains and paths. Avoid setting cookies on entire top level domains such as example.com.

In the token handler pattern, cookies used to access data are only sent to the API endpoints that need them, and the cookie used for token refresh is only sent to a particular endpoint of the OAuth Agent.

text
1234567
set-cookie:
example-rt: <AES-256-GCM encrypted data>
Domain: api.example.com
Path: /refresh
Secure
Http Only
SameSite=strict

Avoid implementing your own single sign-on solution that sets cookies for a top level domain such as example.com. Doing so sends SSO cookies to many endpoints that are not entitled to receive them. Always use an authorization server instead, which will set SSO cookies for a separate dedicated domain.

Limit Cookie Sizes

Cookies are needed to convey identity, but should not be used as a general mechanism for conveying data, due to size limitations. Current browsers have a limit of 4KB per cookie, and some servers, such as NGINX, have an overall 4KB default limit for the cookie header size. Therefore, use small opaque tokens when storing tokens in cookies. The RFC6265 document provides further information on cookie limits, though some user agents may go beyond this and restrict cookie sizes further.

Restricted OAuth Flows

A number of OAuth flows for web applications use iframes that load content from the authorization server domain, or redirect to it, or send Ajax requests. Some user agents, such as Safari, will refuse to send authorization server cookies in these scenarios, preventing these flows from working. Some affected OAuth flows are summarized in the following sections, along with alternative approaches.

Flows that require first-party OAuth cookies

The flows in this section will not work in all browsers with default settings, unless the authorization server is hosted on the same parent domain as the web application's origin.

Checking Login Status

When loading a browser based app, it is possible to spin up a hidden iframe and send a code flow request that includes the OpenID Connect prompt=none parameter. This sends the SSO cookie, and if the user is not already authenticated, a response with an error=login_required parameter is received.

When the user agent refuses to send the SSO cookie, this flow no longer works. In most web scenarios, it remains easy to determine if the user is signed in though, by simply making an HTTP request to the app's backend, to check whether a valid HTTP-only cookie is present.

Silent Token Refresh

Single page applications that use access tokens in the browser can silently refresh tokens by running a complete code flow on a hidden iframe, using prompt=none. This flow can be triggered when an access token expires, or as a background operation, that refreshes the access token before the time in its expires_in field.

Browser based apps that use cookies can handle token refresh in a simpler way. When the app calls an API and receives a response with a 401 HTTP status code, it must call its backend to perform a token refresh, which will rewrite cookies. The app can then retry the API call with the new access token.

Assisted Token Flow

Curity provides a simple solution for single page applications called the assisted token flow. This downloads Javascript code from an endpoint of the authorization server, which runs in an secure iframe and performs a code flow, after which tokens can be sent from the browser.

Due to recent browser restrictions however, this flow may fail, if the user agent refuses to send temporary cookies required to secure the authentication workflow. The assisted token flow may still be useful as a simple solution for internal applications, especially if you can control which browsers are used.

Single Logout Flows

The OpenID Connect specification provides multiple options for OpenID Connect Logout. One such option is session management, which downloads Javascript code from an endpoint of the authorization server. This code then runs in an iframe and frequently polls an endpoint of the authorization server, using the SSO cookie. This enables the app to detect user logout at the authorization server, then ensure that the user is also signed out of the application.

Logout flows may fail if they rely on third-party cookies sent from iframes. Instead, a simpler and more reliable option is to implement RP-initiated logout, using a top level redirect. Multi-tab logout within a web domain can be achieved by setting a loggedOut property in the user agent's local storage, then listening for storage event changes on other browser windows. See the SPA using token handler for an example.

Web Cookies and Navigation

Once your web cookie security is implemented securely, and you are using reliable OAuth flows, you may also need to design solutions for multi-application scenarios. This section provides recommendations for securely managing various types of navigation.

Navigation Across Multiple Apps

It is possible to share cookies across multiple web applications. Doing so avoids OpenID Connect redirects when users navigate across applications, which can provide a seamless user experience. In many cases though, this is not a good security design.

For OAuth secured browser based apps, cookies map to OAuth tokens representing privileges. When navigating across business areas, ensure that users of each application run with least privilege. In the following example, a marketing user's cookie maps to a token that includes a finance scope:

An example of a marketing user's cookie maps to a token that includes a finance scope

A more secure design is to implement navigation across business areas using single sign-on. Cookies sent to APIs will then map to tokens designed with least privilege, while also providing a good user experience. Use separate backends to achieve this, and ensure that each app sends cookies to APIs via a different domain or path:

Implementing navigation across business areas using single sign-on

Cookie sharing is a better choice in micro-frontend architectures. A browser based app for a single business area could be split across teams, to keep code sizes manageable. Multiple micro-frontends could then be deployed, to run at paths such as /marketing1 and /marketing2. This is easy to enable when API driven cookie issuing is used, since the cookie issuing technology is external to each application, and conflicts are thus avoided.

Widgets and Mashups

It is possible to use iframe or div HTML elements as widgets that consume external content, as part of a web mashup solution. With recent browser restrictions this will no longer work reliably in all browsers. If a third-party cookie is used, the user agent may not send it. Some alternative designs, such as using access tokens directly in the browser, result in suboptimal security.

Third party frame

The preferred option is to instead design such solutions to use a first-party iframe or div HTML element, that sends first-party requests to your own backend, so that only same-site cookies are used. Consider using a reverse proxy in your own backend, to route such requests to the third party domain. The reverse proxy can also attach a secure B2B credential, such as an access token or client certificate.

Mobile Web Navigation

It is common for organizations to want users to navigate seamlessly from their mobile app to a browser based app. In some cases, single sign-on may not work due to web and mobile SSO cookies being stored in different cookie jars, leading to a double login. Other interoperability solutions are not obvious, when mobile apps use tokens, while web applications use secure cookies.

In all cases though, browser based apps should continue to use the security recommendations from this article, and use the most secure cookies in mobile browsers. For further techniques on mobile web interoperability, see the nonce authenticator pattern article, and the Mobile Web SSO code example.

Conclusion

Browsers and servers must protect against many security threats when using cookies, and you must implement up to date cookie best practices in your applications. Recent cookie restrictions also need to be managed, which help to protect privacy, but may also prevent some web design patterns from working. To overcome these barriers, use the following main techniques, to ensure predictable results:

  • Use only the latest and most secure cookies in your browser based apps
  • Issue server-side cookies using tested web cookie solutions, provided by security experts
  • Use a top level window for OAuth redirects, to ensure that third-party cookies are handled reliably
  • For other secure communication from the browser to third-party domains, route requests via your backend
Photo of Gary Archer

Gary Archer

Product Marketing Engineer at Curity

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