diff --git a/draft-ietf-oauth-browser-based-apps.md b/draft-ietf-oauth-browser-based-apps.md index 22f4c1f..bc205b5 100644 --- a/draft-ietf-oauth-browser-based-apps.md +++ b/draft-ietf-oauth-browser-based-apps.md @@ -226,7 +226,7 @@ The remainder of this specification will refer back to these attack scenarios an Malicious JavaScript Payloads {#payloads} ----------------------------- -This section presents several malicious scenarios that an attacker can execute once they have found a vulnerability that allows the execution of malicious JavaScripot code. The attack scenarios range from extremely trivial ({{payload-single-theft}}) to highly sophisticated ({{payload-new-flow}}). Note that this enumeration is non-exhaustive and presented in no particular order. +This section presents several malicious scenarios that an attacker can execute once they have found a vulnerability that allows the execution of malicious JavaScript code. The attack scenarios range from extremely trivial ({{payload-single-theft}}) to highly sophisticated ({{payload-new-flow}}). Note that this enumeration is non-exhaustive and presented in no particular order. ### Single-Execution Token Theft {#payload-single-theft} @@ -273,19 +273,19 @@ The most important takeaway from this scenario is that it runs a new OAuth flow This attack scenario is possible because the security of public browser-based OAuth 2.0 clients relies entirely on the redirect URI and application's origin. When the attacker can execute malicious JavaScript code in the application's execution context, they effectively control the application's origin, and by extension, the redirect URI. This attack scenario uses a silent iframe-based flow, which is the same mechanism most browser-based apps use to bootstrap their authentication state. Since the attacker controls the application in the browser, the attacker's authorization code flow is indistinguishable from a legitimate authorization code flow. -There are no security mechanisms for frontend applications that counter this attack scenario. Short access token lifetimes and refresh token rotation are ineffective, since the attacker has a fresh, independent set of tokens. Advanced security mechanism, such as DPoP ({{dpop}}) are equally ineffective, since the attacker can use their own key pair to setup and use DPoP for the newly obtained tokens. +There are no practical security mechanisms for frontend applications that counter this attack scenario. Short access token lifetimes and refresh token rotation are ineffective, since the attacker has a fresh, independent set of tokens. Advanced security mechanism, such as DPoP ({{DPoP}}) are equally ineffective, since the attacker can use their own key pair to setup and use DPoP for the newly obtained tokens. Additionally, authorization server behavior that would force every authorization code flow to require user interaction would significantly impact widely-established patterns, such as silently bootstrapping an application with tokens, or Single Sign-On across multiple related applications. ### Proxying Requests via the User's Browser {#payload-proxy} -This attack scenario takes a different approach. Insatead of abusing the application to obtain tokens, the attacker will send requests directly from within the OAuth client application running in the user's browser. The requests sent by the attacker are indistinguishable from requests sent by the legitimate application. This scenario consists of the following steps: +This attack scenario takes a different approach. Instead of abusing the application to obtain tokens, the attacker will send requests directly from within the OAuth client application running in the user's browser. The requests sent by the attacker are indistinguishable from requests sent by the legitimate application. This scenario consists of the following steps: - Execute malicious JS code - Send a request to a resource server and process the response To authorize the requests to the resource server, the attacker simply mimics the behavior of the client application. For example, when a client application programmatically attaches an access token to outgoing requests, the attacker does the same. Should the client application rely on an external component to augment the request with the proper access token, then this external component will also augment the attacker's request. -This attack pattern is well-known and also occurs with traditional applications using HttpOnly session cookies. It is commonly accepted that this scenario cannot be stopped or prevented by application-level security measures. For example, the DPoP specification ({{dpop}}) explicitly considers this attack scenario to be out of scope. +This attack pattern is well-known and also occurs with traditional applications using HttpOnly session cookies. It is commonly accepted that this scenario cannot be stopped or prevented by application-level security measures. For example, the DPoP specification ({{DPoP}}) explicitly considers this attack scenario to be out of scope. @@ -307,9 +307,12 @@ The attack is only stopped when the authorization server refuses a refresh token If the attacker obtains a valid access token, they gain the ability to impersonate the user in a request to a resource server. Concretely, possession of an access token allows the attacker to send arbitrary requests to any resource server that considers the access token to be valid. In essence, abusing a stolen access token enables short-term impersonation of the user to resource servers. -The attack ends when the access token expires, or, in case of a reference token, when a token is revoked with the authorization server. In a typical browser-based OAuth client, access token lifetimes can be quite short, ranging from minutes to hours. +The attack ends when the access token expires or when a token is revoked with the authorization server. In a typical browser-based OAuth client, access token lifetimes can be quite short, ranging from minutes to hours. + +Note that the possession of the access token allows its unrestricted use by the attacker. The attacker can send arbitrary requests to resource servers, using any HTTP method, destination URL, header values, or body. + +The application can use DPoP to ensure its access tokens are bound to non-exportable key linked to the browser. In that case, it becomes significantly harder for the attacker to abuse stolen access tokens. More specifically, with DPoP, the attacker can only abuse stolen application tokens by carrying out an online attack, where the proofs are calculated in the user's browser. This attack is described in detail in section 11.4 of the {{DPoP}} specification. Additionally, when the attacker obtains a fresh set of tokens, as described in {{payload-new-flow}}, they can set up DPoP for these tokens using an attacker-controlled secret. In that case, the attacker is again free to abuse this newly obtained access token without restrictions. -Note that the possession of the access token gives allows its unrestricted use by the attacker. The attacker can send arbitrary requests to resource servers, using any HTTP method, destination URL, header values, or body. @@ -334,12 +337,380 @@ There are three main architectural patterns available when building browser-base Each of these architecture patterns offer a different trade-off between security and simplicity. The patterns in this section are presented in decreasing order of security. -TODO ADD BFF -TODO ADD TMI-BFF +Backend For Frontend (BFF) {#pattern-bff} +-------------------------- + +This section describes the architecture of a JavaScript application that relies on a backend component to handle all OAuth responsibilities and API interactions. The BFF has three core responsibilities: +1. The BFF interacts as a confidential OAuth client with the authorization server +2. The BFF manages OAuth access and refresh tokens, making them inaccessible by the JavaScript application +3. The BFF proxies all requests to a resource server, augmenting them with the correct access token before forwarding them to the resource server + +If an attacker is able to execute malicious code within the JavaScript application, the application architecture is able to withstand most of the payload scenarios discussed before. Since tokens are only available to the BFF, there are no tokens available to extract from JavaScript (Payload {{payload-single-theft}} and {{payload-persistent-theft}}). The BFF is a confidential client, which prevents the attacker from running a new flow within the browser (Payload {{payload-new-flow}}). Since the malicious JavaScript code still runs within the application's execution context, the attacker is able to send requests to the BFF from within the user's browser (Payload {{payload-proxy}}). + + +### Application Architecture + + +-------------+ +--------------+ +--------------+ + | | | | | | + |Authorization| | Token | | Resource | + | Endpoint | | Endpoint | | Server | + | | | | | | + +-------------+ +--------------+ +--------------+ + + ^ ^ ^ + | (F)| (K)| + | v v + + | +-----------------------------------+ + | | | + | | Backend for Frontend (BFF) | + (D)| | | + | +-----------------------------------+ + | + | ^ ^ ^ + ^ + + | (B,I)| (C)| (E)| (G)| (J)| |(L) + v v v + v + v + + +-----------------+ +-------------------------------------------------+ + | | (A,H) | | + | Static Web Host | +-----> | Browser | + | | | | + +-----------------+ +-------------------------------------------------+ + + +In this architecture, the JavaScript code is first loaded from a static web host into the browser (A), and the application then runs in the browser. The application checks with the BFF if there is an active session (B). If an active session is found, the application resumes its authenticated state and skips forward to step J. + +When no active session is found, the JavaScript application calls out to the BFF (C) to initiate the Authorization Code flow with the PKCE +extension (described in {{TODO}}), to which the BFF responds by redirecting the browser to the authorization endpoint (D). When the user is redirected back, the browser delivers the authorization code to the BFF (E), where the BFF can then exchange it for tokens at the token endpoint (F) using its client secret and PKCE code verifier. + +The BFF associates the obtained tokens with the user's session (See {{pattern-bff-sessions}}) and includes the relevant information in a cookie that is included in the response to the browser (G). This response to the browser will also trigger the reloading of the JavaScript application (H). When this application reloads, it will check with the BFF for an existing session (I), allowing the JavaScript application to resume its authenticated state. + +When the JavaScript application in the browser wants to make a request to the resource server, it sends a request to the corresponding endpoint on the BFF (J). This request will include the cookie set in step G, allowing the BFF to obtain the proper tokens for this user's session. The BFF removes the cookie from the request, attaches the user's access token to the request, and forwards it to the actual resource server (K). The BFF then forwards the response back to the browser-based application (L). + + +### Implementation Details + +#### Refresh Tokens + +It is recommended to use both access tokens and refresh tokens, as it enables access tokens to be short-lived and minimally scoped (e.g., using Resource Indicators TODO REF). When using refresh tokens, the BFF obtains the refresh token in step F and associates it with the user's session. + +If the BFF notices that the user's access token has expired and the BFF has a refresh token, it can run a Refresh Token flow to obtain a fresh access token. These steps are not shown in the diagram, but would occur between step J and K. Note that this Refresh Token flow involves a confidential client, thus requires client authentication. + +When the refresh token expires, there is no way to recover without running an entirely new Authorization Code flow. Therefore, it is recommended to configure the lifetime of the cookie-based session to be equal to the maximum lifetime of the refresh token. Additionally, when the BFF learns that a refresh token for an active session is no longer valid, it is recommended to invalidate the session. + + +#### Cookie-based Session Management {#pattern-bff-sessions} + +The BFF relies on traditional browser cookies to keep track of the user's session, which is used to access the user's tokens. Cookie-based sessions, both server-side and client-side, are often thought of as problematic. + +Server-side sessions only expose a session identifier and keep all data on the server. Doing so ensures a great level of control over active sessions, along with the possibility to revoke any session at will. The downside of this approach is the impact on scalability, requiring solutions such as sticky sessions, or session replication. Given these downsides, using server-side sessions with a BFF is only recommended in small-scale scenarios. + +Client-side sessions push all data to the browser in a signed, and optionally encrypted, object. This pattern absolves the server of keeping track of any session data, but severely limits control over active sessions and makes it difficult to handle session revocation. However, when client-side sessions are used in the context of a BFF, these properties change significantly. Since the cookie-based session is only used to obtain a user's tokens, all control and revocation properties follow from the use of access tokens and refresh tokens. It suffices to revoke the user's access token and/or refresh token to prevent ongoing access to protected resources, without the need to explicitly invalidate the cookie-based session. + +Best practices to secure the session cookie are discussed in section {{TODO}}. + + +#### Combining OAuth and OpenID Connect {#pattern-bff-oidc} + +The OAuth flow used by this application architecture can be combined with OpenID Connect by including the proper scopes in the authorization request (C). In that case, the BFF will receive an identity token in step F. The BFF can associate the information from the identity token with the user's session and provide it to the JavaScript application in step B or I. + +When needed, the BFF can use the access token associated with the user's session to make requests to the UserInfo endpoint. + + +#### Practical Deployment Scenarios + +Serving the static JavaScript code is a separate responsibility from handling OAuth tokens and proxying requests. In the diagram presented above, the BFF and static web host are shown as two separate entities. In real-world deployment scenarios, these components can be deployed as a single service (i.e., the BFF serving the static JS code), as two separate services (i.e., a CDN and a BFF), or as two components in a single service (i.e., static hosting and serverless functions on a cloud platform). + +Note that it is possible to further customize this architecture to tailor to specific scenarios. For example, an application relying on both internal and external resource servers can choose to host the internal resource server alongside the BFF. In that scenario, requests to the internal resource server are handled directly at the BFF, without the need to proxy requests over the network. Authorization from the point of view of the resource server does not change, as the user's session is internally translated to the access token and its claims. + + + +### Security Considerations + +#### The Authorization Code Grant {#pattern-bff-flow} + +The main benefit of using a BFF is the BFF's ability to act as a confidential client. Therefore, the BFF MUST act as a confidential client. Furthermore, the BFF SHOULD use the OAuth 2.0 Authorization Code grant with PKCE to initiate a request for an access token. Detailed recommendations for confidential clients can be found in {{oauth-security-topics}} Section 2.1.1. + + +#### Cookie Security {#pattern-bff-cookie-security} + +The BFF uses cookies to create a user session, which is directly associated with the user's tokens, either through server-side or client-side session state. Given the sensitive nature of these cookies, they must be properly protected. + +The following cookie security guidelines are relevant for this particular BFF architecture: +- The BFF MUST enable the *Secure* flag for its cookies +- The BFF MUST enable the *HttpOnly* flag for its cookies +- The BFF SHOULD enable the *SameSite=Strict* flag for its cookies +- The BFF SHOULD set its cookie path to */* +- The BFF SHOULD NOT set the *Domain* attribute for cookies +- The BFF SHOULD start the name of its cookies with the *__Host-* prefix (TODO REF: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) + +Additionally, the BFF SHOULD encrypt its cookie contents, to ensure that tokens stored in cookies are never written to disk in plaintext format. This security measure helps to secure the access token against malware that actively scans the user's harddrive to extract sensitive browser artifacts, such as cookies and locally stored data. + +For further guidance on cookie security best practices, we refer to the OWASP Cheat Sheet series (). + + +#### Cross-Site Request Forgery Protections {#pattern-bff-csrf} + +The interactions between the JavaScript application and the BFF rely on cookies for authentication and authorization. Similar to other cookie-based interactions, the BFF is required to account for Cross-Site Request Forgery (CSRF) attacks. + +The BFF MUST implement a proper CSRF defense. The exact mechanism or combination of mechanisms depends on the exact domain where the BFF is deployed, as discussed below. + + +##### SameSite Cookie Attribute + +Configuring the cookies with the *SameSite=Strict* attribute (See {{pattern-bff-cookie-security}}) ensures that the BFF's cookies are only included on same-site requests, and not on potentially malicious cross-site requests. + +This defense is adequate if the BFF is never considered to be same-site with any other applications. However, it falls short when the BFF is hosted alongside other applications within the same parent domain. + +For example, subdomains, such as `https://a.example.com` and `https://b.example.com`, are considered same-site, since they share the same site `example.com`. They are considered cross-origin, since origins consist of the tuple **. As a result, a subdomain takeover attack against `b.example.com` can enable CSRF attacks against the BFF of `a.example.com`. Technically, this attack should be identified as a "Same-Site But Cross-Origin Request Forgery" attack. + + +##### Cross-Origin Resource Sharing (CORS) + +The BFF can rely on CORS as a CSRF defense mechanism. CORS is a security mechanism implemented by browsers that restricts cross-origin JavaScript-based requests, unless the server explicitly approves such a request by setting the proper CORS headers. + +CORS is designed in such a way that every JavaScript-based request that does not mimic a legacy browser interaction, such as a navigation or image load, requires approval by the server through a preflight. Unless the preflight response explicitly approves the request, the browser will refuse to send it. + +Because of this property, the BFF can rely on CORS as a CSRF defense. When the attacker tries to launch a cross-origin request to the BFF from the user's browser, the BFF will not approve the request in the preflight response, causing the browser to block the actual request. Note that the attacker can always launch the request from their own machine, but then the request will not carry the user's cookies, so the attack will fail. + +When relying on CORS as a CSRF defense, it is important to realize that certain requests are possible without a preflight. For such requests, named "Simple Requests" in CORS terminology, the browser will simply send the request and prevent access to the response if the server did not send the proper CORS headers. This behavior is enforced for requests that can be triggered via other means than JavaScript, such as a GET request or a form-based POST request. + +The consquence of this behavior is that certain endpoints of the resource server could become vulnerable to CSRF, even with CORS enabled as a defense. For example, if the resource server is an API that exposes an endpoint to a bodyless POST request, there will be no preflight request and no CSRF defense. + +To avoid such bypasses against the CORS policy, the BFF SHOULD require that every request includes a custom request header. Cross-origin requests with a custom request header always require a preflight, which makes CORS an effective CSRF defense. Implementing this mechanism is as simple as requiring every request to have a static request header, such as `X-CORS-Security: 1`. + +Finally, note that the JavaScript application is often deployed on the same-origin as the BFF. This ensures that legitimate interactions between the frontend and the BFF do not require any preflights, so there's no additional overhead. + + +#### Advanced Security + +All OAuth responsibilities have been moved to the BFF, a server-side component acting as a confidential client. Since server-side applications are more powerful than browser-based applications, it becomes easier to adopt advanced OAuth security practices. Examples include key-based client authentication and sender-constrained tokens. + + +## Threat Analysis + +This section revisits the payloads and consequences from section {{threats}}, and discusses potential additional defenses. + + +#### Attack Payloads and Consequences + +If the attacker has the ability to execute malicious JavaScript code in the application's execution context, the following payloads become relevant attack scenarios: +* Proxying Requests via the User's Browser (See {{payload-proxy}}) + +Note that this attack scenario results in the following consequences: +* Client Hijacking (See {{consequence-hijack}}) + +Unfortunately, client hijacking is an attack scenario that is inherent to the nature of browser-based applications. As a result, nothing will be able to prevent such attacks apart from stopping the execution of malicious JavaScript code in the first place. Techniques that can help to achieve this are following secure coding guidelines, code analysis, and deploying defense-in-depth mechanisms such as Content Security Policy ({{CSP3}}). + +Finally, the BFF is uniquely placed to observe all traffic between the JavaScript application and the resource servers. If a high-security application would prefer to implement anomaly detection or rate limiting, such a BFF would be the ideal place to do so. Such restrictions can further help to mitigate the consequences of client hijacking. + + +#### Mitigated Attack Scenarios + +The other payloads, listed below, are effectively mitigated by the BFF application architecture: +* Single-Execution Token Theft (See {{payload-single-theft}}) +* Persistent Token Theft (See {{payload-persistent-theft}}) +* Acquisition and Extraction of New Tokens (See {{payload-new-flow}}) + +The BFF counters the first two payloads by not exposing any tokens to the browser-based application. Even when the attacker gains full control over the JavaScript application, there are simply no tokens to be stolen. + +The third scenario, where the attacker obtains a fresh set of tokens by running a silent flow, is mitigated by making the BFF a confidential client. Even when the attacker manages to obtain an authorization code, they are prevented from exchanging this code due to the lack of client credentials. Additionally, the use of PKCE prevents other attacks against the authorization code. + +Because of the nature of the BFF, the following two consequences of potential attacks become irrelevant: +* Exploiting Stolen Refresh Tokens (See {{consequence-rt}}) +* Exploiting Stolen Access Tokens (See {{consequence-at}}) + + +#### Summary + +To summarize, the architecture of a BFF is signifantly more complicated than a browser-only application. It requires deploying and operating a server-side BFF component. Additionally, this pattern requires all interactions between the JavaScript application and the resource servers to be proxied by the BFF, which puts a signficant burden on the server-side component. + +However, because of the nature of the BFF architecture pattern, it offers strong security guarantuees. Using a BFF also ensures that the application's attack surfance does not increase by using OAuth. The only viable attack pattern is hijacking the client application in the user's browser, a problem inherent to web applications. + +This architecture is strongly recommended for business applications, sensitive applications, and applications that handle personal data. + + + + +Token-Mediating Backend {#pattern-tmb} +----------------------- + +This section describes the architecture of a JavaScript application that relies on a backend component to handle OAuth responsibilities for obtaining tokens, after which the JavaScript application receives the access token to directly interact with resource servers. + +The token-mediating backend pattern is more lightweight than the BFF pattern (See {{pattern-bff}}), since it does not require the proxying of all requests to a resource server, which improves latency and significantly simplifies deployment. From a security perspective, the token-mediating backend is less secure than a BFF, but still offers significant advantages over an OAuth client application running directly in the browser. + +If an attacker is able to execute malicious code within the JavaScript application, the application architecture is able to prevent the attacker from abusing refresh tokens or obtaining a fresh set of tokens. However, since the access token is directly exposed to the JavaScript application, token theft scenarios fall within the capabilities of the attacker. + + +### Application Architecture + + +-------------+ +--------------+ +--------------+ + | | | | | | + |Authorization| | Token | | Resource | + | Endpoint | | Endpoint | | Server | + | | | | | | + +-------------+ +--------------+ +--------------+ + + ^ ^ ^ + | (F)| | + | v | + | + | +-----------------------+ | + | | | | + | |Token-Mediating Backend| | (J) + (D)| | | | + | +-----------------------+ | + | | + | ^ ^ ^ + | + | (B,I)| (C)| (E)| (G)| | + v v v + v v + + +-----------------+ +-------------------------------------------------+ + | | (A,H) | | + | Static Web Host | +-----> | Browser | + | | | | + +-----------------+ +-------------------------------------------------+ + + +In this architecture, the JavaScript code is first loaded from a static web host into the browser (A), and the application then runs in the browser. The application checks with the token-mediating backend if there is an active session (B). If an active session is found, the application receives the corresponding access token, resumes its authenticated state, and skips forward to step J TODO. + +When no active session is found, the JavaScript application calls out to the token-mediating backend (C) to initiate the Authorization Code flow with the PKCE extension (described in {{TODO}}), to which the token-mediating backend responds by redirecting the browser to the authorization endpoint (D). When the user is redirected back, the browser delivers the authorization code to the token-mediating backend (E), where the token-mediating backend can then exchange it for tokens at the token endpoint (F) using its client secret and PKCE code verifier. + +The token-mediating backend associates the obtained tokens with the user's session (See {{pattern-tmb-session}}) and includes the relevant information in a cookie that is included in the response to the browser (G). This response to the browser will also trigger the reloading of the JavaScript application (H). When this application reloads, it will check with the token-mediating backend for an existing session (I), allowing the JavaScript application to resume its authenticated state and obtain the access token from the token-mediating backend. + +The JavaScript application in the browser can use the access token obtained in step I to directly make requests to the resource server (J). + +Editor's Note: A method of implementing this architecture is described by the {{tmi-bff}} draft, although it is currently an expired individual draft and has not been proposed for adoption to the OAuth Working Group. + + + +### Implementation Details + +#### Refresh Tokens + +It is recommended to use both access tokens and refresh tokens, as it enables access tokens to be short-lived and minimally scoped (e.g., using Resource Indicators TODO REF). When using refresh tokens, the token-mediating backend obtains the refresh token in step F and associates it with the user's session. +If the JavaScript application notices that the user's access token has expired, it can contact the token-mediating backend to request a fresh access token. The token-mediating backend relies on the cookies associated with this request to obtain the user's refresh token and run a Refresh Token flow. These steps are not shown in the diagram. Note that this Refresh Token flow involves a confidential client, thus requires client authentication. -Handling OAuth Responsibilities in the Browser {#pattern-oauth-browser} ----------------------------------------------- +When the refresh token expires, there is no way to recover without running an entirely new Authorization Code flow. Therefore, it is recommended to configure the lifetime of the cookie-based session to be equal to the maximum lifetime of the refresh token. Additionally, when the token-mediating backend learns that a refresh token for an active session is no longer valid, it is recommended to invalidate the session. + + +#### Access Token Scopes + +Depending on the resource servers being accessed and the configuration of scopes at the authorization server, the JavaScript application may wish to request access tokens with different scope configurations. This behavior would allow the JavaScript application to follow the best practice of using minimally-scoped access tokens. + +The JavaScript application can inform the token-mediating backend of the desired scopes when it checks for the active session (Step A/I). It is up to the token-mediating backend to decide if previously obtained access tokens fall within the desired scope criteria. + +It should be noted that this access token caching mechanism at the token-mediating backend can cause scope elevation risks when applied indiscriminately. If the cached access token features a superset of the scopes requested by the frontend, the token-mediating backend SHOULD NOT return it to the frontend; instead it SHOULD use the refresh token to request an access token with the smaller set of scopes from the authorization server. Note that support of such an access token downscoping mechanism is at the discretion of the authorization server. + +The token-mediating backend can use a similar mechanism to downscoping when relying on Resource Indicators (TODO REF) to obtain access token for a specific resource server. + + +#### Cookie-based Session Management {#pattern-tmb-sessions} + +Similar to the BFF, the token-mediating backend relies on traditional browser cookies to keep track of the user's session. The same implementation guidelines and security considerations as for a BFF apply, as discussed in {{pattern-bff-sessions}}. + + +#### Combining OAuth and OpenID Connect + +Similar to a BFF, the token-mediating backend can choose to combine OAuth and OpenID Connect in a single flow. See {{pattern-bff-oidc}} for more details. + + +#### Practical Deployment Scenarios + +Serving the static JavaScript code is a separate responsibility from handling interactions with the authorization server. In the diagram presented above, the token-mediating backend and static web host are shown as two separate entities. In real-world deployment scenarios, these components can be deployed as a single service (i.e., the token-mediating backend serving the static JS code), as two separate services (i.e., a CDN and a token-mediating backend), or as two components in a single service (i.e., static hosting and serverless functions on a cloud platform). + + + +### Security Considerations + +#### The Authorization Code Grant {#pattern-tmb-flow} + +The main benefit of using a token-mediating backend is the backend's ability to act as a confidential client. Therefore, the token-mediating backend MUST act as a confidential client. Furthermore, the token-mediating backend SHOULD use the OAuth 2.0 Authorization Code grant with PKCE to initiate a request for an access token. Detailed recommendations for confidential clients can be found in {{oauth-security-topics}} Section 2.1.1. + + +#### Cookie Security {#pattern-bmf-cookie-security} + +The token-mediating backend uses cookies to create a user session, which is directly associated with the user's tokens, either through server-side or client-side session state. The same cookie security guidelines as for a BFF apply, as discussed in {{pattern-bff-cookie-security}}. + + +#### Cross-Site Request Forgery Protections {#pattern-bmf-csrf} + +The interactions between the JavaScript application and the token-mediating backend rely on cookies for authentication and authorization. Just like a BFF, the token-mediating backend is required to account for Cross-Site Request Forgery (CSRF) attacks. + +Section {{pattern-bff-csrf}} outlines the nuances of various mitigations strategies against CSRF attacks. Specifically for a token-mediating backend, these CSRF defenses only apply to the endpoint or endpoints where the JavaScript application can obtain its access tokens. + + +#### Advanced OAuth Security + +The token-mediating backend is a confidential client running as a server-side component. The token-mediating backend can adopt security best practices for confidential clients, such as key-based client authentication. + + + +## Threat Analysis + +This section revisits the payloads and consequences from section {{threats}}, and discusses potential additional defenses. + + +#### Attack Payloads and Consequences + +If the attacker has the ability to execute malicious JavaScript code in the application's execution context, the following payloads become relevant attack scenarios: +* Single-Execution Token Theft (See {{payload-single-theft}}) for access tokens +* Persistent Token Theft (See {{payload-persistent-theft}}) for access tokens +* Proxying Requests via the User's Browser (See {{payload-proxy}}) + +Note that this attack scenario results in the following consequences: +* Exploiting Stolen Access Tokens (See {{consequence-at}}) +* Client Hijacking (See {{consequence-hijack}}) + +Exposing the access token to the JavaScript application is the core idea behind the architecture pattern of the token-mediating backend. As a result, the access token becomes vulnerable to token theft by malicious JavaScript. + + +#### Mitigated Attack Scenarios + +The other payloads, listed below, are effectively mitigated by the BFF application architecture: +* Single-Execution Token Theft (See {{payload-single-theft}}) for refresh tokens +* Persistent Token Theft (See {{payload-persistent-theft}}) for refresh tokens +* Acquisition and Extraction of New Tokens (See {{payload-new-flow}}) + +The token-mediating backend counters the first two payloads by not exposing the refresh token to the browser-based application. Even when the attacker gains full control over the JavaScript application, there are simply no refresh tokens to be stolen. + +The third scenario, where the attacker obtains a fresh set of tokens by running a silent flow, is mitigated by making the token-mediating backend a confidential client. Even when the attacker manages to obtain an authorization code, they are prevented from exchanging this code due to the lack of client credentials. Additionally, the use of PKCE prevents other attacks against the authorization code. + +Because of the nature of the token-mediating backend, the following consequences of potential attacks become irrelevant: +* Exploiting Stolen Refresh Tokens (See {{consequence-rt}}) + + +#### Additional Defenses + +While this architecture inherently exposes access tokens, there are some additional defenses that can help to increase the security posture of the application. + +##### Secure Token Storage + +Given the nature of the token-mediating backend pattern, there is no need for persistent token storage in the browser. When needed ,the application can always use its cookie-based session to obtain an access token from the token-mediating backend. Section {{token_storage}} provides more details on the security properteis of various storage mechanisms in the browser. + +Note that even when the access token is stored out of reach of malicious JavaScript code, the attacker still has the ability to request the access token from the token-mediating backend. + + +##### Using Sender-Constrained Tokens + +Using sender-constrained access tokens is not trivial in this architecture. The token-mediating backend is responsible for exchanging an authorization code or refresh token for an access token, but the JavaScript application will use the access token. Using a mechanism such as {{DPoP}} would require proof generation for a request to the authorization server in the JavaScript application, but use of that proof by the token-mediating backend. + + +#### Summary + +To summarize, the architecture of a token-mediating backend is more complicated than a browser-only application, but less complicated than running a proxying BFF. Similar to complexity, the security properties offered by the token-mediating backend lie somewhere between using a BFF and running a browswer-only application. + +A token-mediating backend addresses typical scenarios that grant the attacker long-term access on behalf of the user. However, due to the consequence of access token theft, the attacker still has the ability to gain direct access to resource servers. + +When considering a token-mediating backend architecture, it is strongly recommended to go the extra mile and adopt a full BFF as discussed in {{pattern-bff}}. Only when the use cases or system requirements would prevent the use of a proxying BFF should the token-mediating backend be considered as viable alternative. + + + + + +Browser-based OAuth 2.0 client {#pattern-oauth-browser} +------------------------------ This section describes the architecture of a JavaScript application that acts as the OAuth 2.0 client, handling all OAuth responsibilties in the browser. As a result, the browser-based application obtains tokens from the authorization server, without the involvement of a backend component. @@ -395,7 +766,7 @@ described in this section. In summary, browser-based applications using the Authorization Code flow: * MUST use PKCE ({{RFC7636}}) when obtaining an access token ({{auth_code_request}}) -* MUST Protect themselves against CSRF attacks ({{csrf_protection}}) by either: +* MUST Protect themselves against CSRF attacks ({{pattern-oauth-browser-csrf}}) by either: * ensuring the authorization server supports PKCE, or * by using the OAuth 2.0 "state" parameter or the OpenID Connect "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 ({{auth_code_redirect}}) @@ -432,7 +803,7 @@ Authorization servers MUST require an exact match of a registered redirect URI as described in {{oauth-security-topics}} Section 4.1.1. This helps to prevent attacks targeting the authorization code. -##### Cross-Site Request Forgery Protections {#csrf_protection} +##### Cross-Site Request Forgery Protections {#pattern-oauth-browser-csrf} Browser-based applications MUST prevent CSRF attacks against their redirect URI. This can be accomplished by any of the below: @@ -552,157 +923,6 @@ This architecture is not recommended for business applications, sensitive applic - -############################################################################################## -############################################################################################## -############################################################################################## -############################################################################################## -####################################### TODO OLD STUFF ####################################### -############################################################################################## -############################################################################################## -############################################################################################## -############################################################################################## - - -Backend For Frontend (BFF) Proxy {#bff-proxy} --------------------------------- - - +-------------+ +--------------+ +---------------+ - | | | | | | - |Authorization| | Token | | Resource | - | Endpoint | | Endpoint | | Server | - | | | | | | - +-------------+ +--------------+ +---------------+ - - ^ ^ ^ - | (D)| (G)| - | v v - | - | +--------------------------------------+ - | | | - | | Backend for Frontend Proxy (BFF) | - (B)| | | - | +--------------------------------------+ - | - | ^ ^ + ^ + - | (A)| (C)| (E)| (F)| |(H) - v v + v + v - - +-------------------------------------------------+ - | | - | Browser | - | | - +-------------------------------------------------+ - -In this architecture, commonly referred to as "backend for frontend" or "BFF", the JavaScript code is loaded from a BFF Proxy server (A) that has the ability to execute code and handle the full OAuth flow itself. This enables the ability to keep -the request to obtain an access token outside the JavaScript application. - -Note that this BFF Proxy is not the Resource Server, it is the OAuth client and would be later accessing data at a separate resource server after obtaining tokens. - -In this case, the BFF Proxy initiates the OAuth flow itself, by redirecting the browser to the authorization endpoint (B). When the user is redirected back, the browser delivers the authorization code to the BFF Proxy (C), where it can then exchange it for an access token at the token endpoint (D) using its client secret and PKCE code verifier. -The BFF Proxy then keeps the access token and refresh token stored internally, and creates a separate session with the browser-based app via a -traditional browser cookie (E). - -When the JavaScript application in the browser wants to make a request to the Resource Server, -it instead makes the request to the BFF Proxy (F), and the BFF Proxy will -make the request with the access token to the Resource Server (G), and forward the response (H) -back to the browser. - -(Common examples of this architecture are an Angular front-end with a .NET backend, or -a React front-end with a Spring Boot backend.) - -The BFF Proxy SHOULD be considered a confidential client, and issued its own client secret. The BFF Proxy SHOULD use the OAuth 2.0 Authorization Code grant with PKCE to initiate a request for an access token. Detailed recommendations for confidential clients can be found in {{oauth-security-topics}} Section 2.1.1. - -In this scenario, the connection between the browser and BFF Proxy SHOULD be a -session cookie provided by the BFF Proxy. - -While the security of this model is strong, since the OAuth tokens are never sent to the browser, there are performance and scalability implications of deploying a BFF proxy server and routing all JS requests through the server. If routing every API request through the BFF proxy is prohibitive, you may wish to consider one of the alternative architectures below. - -### Security considerations - -Security of the connection between code running in the browser and this BFF Proxy is -assumed to utilize browser-level protection mechanisms. Details are out of scope of -this document, but many recommendations can be found in the OWASP Cheat Sheet series (), -such as setting an HTTP-only and `Secure` cookie to authenticate the session between the -browser and BFF Proxy. Additionally, cookies MUST be protected from leakage by other means, such as logs. - -In this architecture, tokens are never sent to the front-end and are never accessible by any JavaScript code, so it fully protects against XSS attackers stealing tokens. However, an XSS attacker may still be able to make authenticated requests to the BFF Proxy which will in turn make requests to the resource server including the user's legitimate token. While the attacker is unable to extract and use the access token elsewhere, they could still effectively make authenticated requests to the resource server. - - -Token-Mediating Backend {#tm-backend} ------------------------ - -TODO: Refer to token_storage for storing tokens in the browser - -An alternative to a full BFF where all resource requests go through the backend is to use a token-mediating backend which obtains the tokens and then forwards the tokens to the browser. - - +-------------+ +--------------+ +---------------+ - | | | | | | - |Authorization| | Token | | Resource | - | Endpoint | | Endpoint | | Server | - | | | | | | - +-------------+ +--------------+ +---------------+ - - ^ ^ ^ - | (D)| | - | v | - | | - | +-------------------------+ | - | | | | - | | Token-Mediating Backend | | - (B)| | | | - | +-------------------------+ | - | | - | ^ ^ + | - | (A)| (C)| (E)| (F)| - v v + v + - - +-------------------------------------------------+ - | | - | Browser | - | | - +-------------------------------------------------+ - - -The frontend code makes a request to the Token-Mediating Backend (A), and the backend initiates the OAuth flow itself, by redirecting the browser to the authorization endpoint (B). When the user is redirected back, the browser delivers the authorization code to the application server (C), where it can then exchange it for an access token at the token endpoint (D) using its client secret and PKCE code verifier. The backend delivers the tokens to the browser (E), which stores them for later use. The browser makes requests to the resource server directly (F) including the token it has stored. - -The main advantage this architecture provides over the full BFF architecture previously described is that the backend service is only involved in the acquisition of tokens, and doesn't have to proxy every request in the future. Routing every API call through a backend can be expensive in terms of performance and latency, and can create challenges in deploying the application across many regions. Instead, routing only the token acquisition through a backend means fewer requests are made to the backend. This improves the performance and reduces the latency of requests from the frontend, and reduces the amount of infrastructure needed in the backend. - -Similar to the previously described BFF Proxy pattern, The Token-Mediating Backend SHOULD be considered a confidential client, and issued its own client secret. The Token-Mediating Backend SHOULD use the OAuth 2.0 Authorization Code grant with PKCE to initiate a request for an access token. Detailed recommendations for confidential clients can be found in {{oauth-security-topics}} Section 2.1.1. - -In this scenario, the connection between the browser and Token-Mediating Backend SHOULD be a session cookie provided by the backend. - -The Token-Mediating Backend SHOULD cache tokens it obtains from the authorization server such that when the frontend needs to obtain new tokens, it can do so without the additional round trip to the authorization server if the tokens are still valid. - -The frontend SHOULD NOT persist tokens in local storage or similar mechanisms; instead, the frontend SHOULD store tokens only in memory, and make a new request to the backend if no tokens exist. This provides fewer attack vectors for token exfiltration should an XSS attack be successful. - -Editor's Note: A method of implementing this architecture is described by the {{tmi-bff}} draft, although it is currently an expired individual draft and has not been proposed for adoption to the OAuth Working Group. - - -### Security Considerations - -If the backend caches tokens from the authorization server, it presents scope elevation risks if applied indiscriminately. If the token cached by the authorization server features a superset of the scopes requested by the frontend, the backend SHOULD NOT return it to the frontend; instead it SHOULD perform a new request with the smaller set of scopes to the authorization server. - -In the case of a successful XSS attack, the attacker may be able to access the tokens if the tokens are persisted in the frontend, but is less likely to be able to access the tokens if they are stored only in memory. However, a successful XSS attack will also allow the attacker to call the Token-Mediating Backend itself to retrieve the cached token or start a new OAuth flow. - - - - -############################################################################################## -############################################################################################## -############################################################################################## -############################################################################################## -##################################### TODO END OLD STUFF ##################################### -############################################################################################## -############################################################################################## -############################################################################################## -############################################################################################## - - - - - - Discouraged and Deprecated Architecture Patterns ================================================