How to Take Control of Cookies in OAuth-Secured Web Apps
Cookies are a convenient method for browser-based applications to communicate the user's authenticated identity to the backend. But although issuing cookies only requires a few lines of code in a backend, cookies are more complex than they seem. You may need to deal with both first-party and third-party cookies. Doing so can also impact the web developer setup and how you design deployment domains. You may also need a large-scale design to cover multiple apps, various types of navigation, and cookie isolation. This post provides some techniques you can use to enable the best browser security without compromising your web architecture.
Unauthenticated vs. Authenticated Browser-Based Apps
It can be challenging to develop apps that run in the browser and also implement your preferred architecture. Some apps, like a public news site, can be relatively straightforward to develop. For example, a frontend stack may be combined with server-side rendering (SSR). HTML and JavaScript code can load and present the same backend data for all users. The resulting content can then be deployed to a content delivery network (CDN) to reduce download latency and enable good global web performance. The site would also be able to achieve good search engine optimization (SEO).
Many browser-based apps must instead deal with user logins and personalized data, requiring additional security. The most powerful way to implement this type of web app is to use the OAuth 2.0 and OpenID Connect protocols. Doing so enables rich authentication behaviors, after which the app can call APIs with an access token. This token contains the information the API needs to authorize requests for secured resources. SEO is not usually relevant to secured apps since search bots cannot reach secured views. Yet productive frontend-focused web development and good web performance will usually remain important.
Hardening Security in the Browser
Browser security is a complex topic since you must protect against various threats. It is recommended to follow a secure development lifecycle (SDLC) that addresses the OWASP top 10 web application security risks. If you follow mainstream best practices, you will also be in the best position to answer difficult questions from stakeholders about threats such as cross-site scripting (XSS).
One of the main concerns about OAuth-secured web apps is token exfiltration, where a malicious party can potentially steal access tokens using JavaScript and then access your APIs. OAuth for browser-based apps discusses several ways in which tokens can potentially be intercepted and recommends some architecture patterns. For example, adding an application cookie layer is usually considered safest so that tokens aren't revealed to JavaScript code. Instead, the browser only uses the most secure HTTP-only cookies as an API message credential.
OAuth Cookies and Same-Site Restrictions
In an OAuth-secured web app, you implement a code flow to authenticate users at the authorization server. This starts when the web app sends an authorization request. The parameters received are validated, after which the authorization server issues a temporary cookie. The web app resends the cookie in subsequent HTTP requests until authentication completes. A single sign-on (SSO) cookie is then issued.
Cookies set by the authorization server are third-party, since the web app runs in a different domain than the authorization server, which is usually the case. However, in the last few years, browsers have added aggressive restrictions to drop third-party cookies to prevent unwelcome tracking and to protect user privacy. The rules are defined in recent updates to RFC6265, which describes how cookies are used for HTTP state management and how third-party cookies should be processed. Some browsers go beyond these rules to implement stricter checks.
The golden rule for using OAuth cookies correctly is to only perform authorization redirects on a top-level window. OAuth cookies are then sent reliably, and a visible indication is presented to the user of which login domain they are interacting with. Avoid sending OAuth cookies from an iframe or using an Ajax request, since browser restrictions will likely result in cookies being dropped. Some OAuth flows, such as silent token renewal on a hidden iframe, or running a session management iframe, no longer work reliably in all current browsers.
Cookies for Single-Page Applications
When developing single page applications (SPA), one way to issue cookies is to use a backend for frontend (BFF). This layer can handle the OAuth and cookie-issuing work. It is also possible to use a website where web developers run a cookie-issuing backend that also serves static content. This can be problematic, however, if you aim for a frontend focus since web developers now need to manage multiple backend responsibilities for all future development.
A cleaner option for managing OAuth and cookies in an SPA is via utility API components. You must then ensure that the API domain shares the same site as the web domain so that cookies issued to the SPA remain first-party and are not impacted by browser restrictions. This type of setup enables web developers to connect to remote endpoints that perform the backend work. Doing so also allows you to externalize security plumbing from your code in the cleanest ways to enable a deployment similar to the following diagram. For further information on API-driven cookie issuing, see the token handler overview.
Scaling Cookie Architectures
Real-world web architectures often need to scale the use of cookies across multiple applications. Start by designing which of these will share cookies and which will not. In OAuth-secured web apps, cookies sent in API requests map to underlying tokens, containing scopes and claims. When navigating to a new app with different business permissions, each app should get its own separate cookies and run with the least privilege. Conversely, micro-UIs that break down a large web code base can often share cookies.
API-driven cookie issuing enables some exciting behaviors when managing cookies for multiple web apps. When sharing cookies, it allows you to avoid web backend cookie issuing incompatibilities, since web backends no longer issue cookies. It also lets you design cookie domains and paths distinct from the web apps' physical hosting.
In the following example, web apps for multiple business areas are deployed as a product suite, using different paths under the same web origin. Least privilege cookies can be enabled without changing the web deployment, using different BFFs, located at different API domains or paths. This also keeps cookies isolated between apps, since it is essential that each app only ever sends its own cookies to APIs.
Conclusion
When building a secure web app, use OAuth 2.0 and OpenID Connect to enable many ways to authenticate and the richest security capabilities. Only ever attempt to use OAuth third-party cookies using top-level redirects. Follow the current best practice of using the most secure HTTP-only cookies as API message credentials. When designing how to issue cookies, also consider how it will impact the productivity of web developers and how you will deploy static content for the best performance.
It is also recommended to plan how to grow your cookie-based architecture to multiple web apps. This should include designing web and API domains, along with cookie paths. Also, think through the navigation scenarios, including multiple web apps, the embedding of apps, and web integration on mobile devices. In each case, you should continue to follow best practices for browser-based apps. For more on these topics, see the extended article on OAuth and same-site cookie best practices.