Description
The OAuth 2.0 for Browser-Based Apps specification details the security considerations and best practices when developing browser-based applications that use OAuth 2.0.
The purpose of this issue is to:
- Provide a summary of the key points detailed in the specification.
- List the currently supported and unsupported features from the specification.
Overview
The current best practice for browser-based applications is to use the OAuth 2.0 Authorization Code flow with PKCE.
Browser-based applications:
- MUST use the OAuth 2.0 Authorization Code flow with the PKCE extension when obtaining an access token
- MUST Protect themselves against CSRF attacks by either:
- ensuring the authorization server supports PKCE, or
- by using the OAuth 2.0 "state" parameter or the OpenID Connect 1.0 "nonce" parameter to carry one-time use CSRF tokens
- MUST Register one or more redirect URIs, and use only exact registered redirect URIs in authorization requests
OAuth 2.0 Authorization Servers supporting browser-based applications:
- MUST Require exact matching of registered redirect URIs
- MUST Support the PKCE extension
- MUST NOT issue access tokens in the authorization response
Application Architecture Patterns
There are three primary architectural patterns available when building browser-based applications:
- a JavaScript application that has methods of sharing data with resource servers, such as using common-domain cookies
- a JavaScript application with a backend
- a JavaScript application with no backend, accessing resource servers directly
These three architectures have different use cases and considerations.
Common-domain cookies
- Used in simple system architectures, where the client application, authorization server and resource server(s) share the same domain and can share HTTP-only secure cookies that store the access token and refresh token.
- This architecture pattern is not widely used, as OAuth was originally created for third-party or federated access to APIs.
Backend component
- This architecture pattern is commonly referred to as "backend for frontend" or "BFF".
- The JavaScript code (frontend) is loaded from the server that also has a backend component deployed alongside it. This enables the ability to perform OAuth flows in the backend component and store the access token and refresh token in the "secure" server environment instead of the "insecure" frontend (browser) environment.
- The backend component uses a confidential client when performing the OAuth 2.0 Authorization Code flow with PKCE.
- The server establishes a session between the frontend and backend using a session cookie.
- A request to a resource server is initiated from the frontend to the backend component and then to the resource server. The backend component acts as a mediator.
Frontend-only component
- The JavaScript code (frontend) is loaded from a static web host into the browser, and the application then runs in the browser. This application is considered a public client, since there is no way to issue it a client secret and there is no other secure client authentication mechanism available in the browser.
- The JavaScript application is responsible for storing the access token (and optional refresh token), however, there is no standard browser API that allows to store tokens in a completely secure way.
- The JavaScript application directly interacts with the Authorization Server and Resource Server.
- The Authorization Server and Resource Server MUST support the necessary CORS headers to enable the JavaScript application to make requests from the domain on which the JavaScript is executing.
Refresh Tokens
With public clients, the risk of a leaked refresh token is greater than leaked access tokens, since an attacker may be able to continue using the stolen refresh token to obtain new access tokens potentially without being detectable by the Authorization Server.
Browser-based applications provide an attacker with several opportunities by which a refresh token can be leaked, just as with access tokens. As such, these applications are considered a higher risk for handling refresh tokens.
Authorization Servers may choose whether or not to issue refresh tokens to browser-based applications. If refresh tokens are issued, Authorization Servers MUST conform to the following:
- MUST either rotate refresh tokens on each use OR use sender-constrained refresh tokens
- MUST either set a maximum lifetime on refresh tokens OR expire if the refresh token has not been used within some amount of time
- MUST NOT extend the lifetime of the new refresh token beyond the lifetime of the initial refresh token
Refresh Token Rotation
The Authorization Server issues a new refresh token with every access token refresh response. The previous refresh token is invalidated but information about the relationship is retained by the Authorization Server. If a refresh token is compromised and subsequently used by both the attacker and the legitimate client, one of them will present an invalidated refresh token, which will inform the Authorization Server of the breach. The Authorization Server cannot determine which party submitted the invalid refresh token, but it will revoke the active refresh token. This stops the attack at the cost of forcing the legitimate client to obtain a fresh authorization grant.
Sender-Constrained Refresh Tokens
Sender-constrained refresh tokens scope the applicability of a refresh token to a certain sender. This sender is obliged to demonstrate knowledge of a certain secret as prerequisite for the acceptance of the refresh token at the Authorization Server.
A typical flow looks like this:
- The Authorization Server associates data with the refresh token that binds this particular token to a certain client. The binding can utilize the client identity, but in most cases the Authorization Server utilizes key material (or data derived from the key material) known to the client.
- This key material must be distributed somehow. Either the key material already exists before the Authorization Server creates the binding or the Authorization Server creates ephemeral keys. The way pre-existing key material is distributed varies among the different approaches. For example, X.509 Certificates can be used in which case the distribution happens explicitly during the enrolment process. Or the key material is created and distributed at the TLS layer, in which case it might automatically happen during the setup of a TLS connection.
- The Authorization Server must implement the actual proof of possession check during a refresh access token request. This is typically done on the application level, often tied to specific material provided by transport layer (e.g., TLS). The Authorization Server must also ensure that replay of the proof of possession is not possible.
There exists several proposals to demonstrate the proof of possession in the scope of the OAuth working group:
- OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
- OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP)
- OAuth 2.0 Token Binding
OAuth 2.0 Mutual-TLS is the most widely implemented and the only standardized sender-constraining method. The use of OAuth 2.0 Mutual-TLS is RECOMMENDED.
Current Feature Support for Browser-Based Apps
Supported
- OAuth 2.0 Authorization Code flow with PKCE
- OAuth 2.0 "state" parameter
- OpenID Connect 1.0 Authorization Code flow with PKCE
- OpenID Connect 1.0 "nonce" parameter
- Exact matching of registered redirect URIs
Unsupported
- Refresh Tokens for Public Clients
Refresh Tokens for Public Clients
There are no plans to implement refresh tokens for Public Clients, as there are no browser APIs that allow refresh tokens to be stored in a secure way, which would result in an increased attack surface.
Rotating refresh tokens would help reduce the attack surface, however, it cannot eliminate it. For example, a leaked refresh token can be used by an attacker (before the legitimate client) to refresh an access token and ultimately access the protected resources. The exposure would last until the refreshed access token is expired or invalidated.
A sender-constrained refresh token prevents Token Replay and therefore is RECOMMENDED. However, this requires key material (Public-Private key pair) to be configured in the browser agent and used by the browser-based application, resulting in a complicated setup.
Final Recommendation
After a detailed review of OAuth 2.0 for Browser-Based Apps, OAuth 2.0 Security Best Current Practice and OAuth 2.0 Threat Model and Security Considerations, our recommendation when developing browser-based applications is to use the OAuth 2.0 Authorization Code flow with PKCE and a confidential client.
This can be implemented using either of the two architectural patterns: