RFC 9700

Best Current Practice for OAuth 2.0 Security

πŸ“… published January 2025, from ietf

Best Current Practice for OAuth 2.0 Security πŸŽ€βœ¨

Okay bestie, you're about to get the tea on OAuth security, and it's giving serious energy πŸ’… This is RFC 9700, a Best Current Practice (BCP 240) from January 2025. Authors: T. Lodderstedt (SPRIND), J. Bradley (Yubico), A. Labunets (Independent Researcher), and D. Fett (Authlete). ISSN 2070-1721. πŸ¦‹

Heads up: this one updates RFC 6749, RFC 6750, and RFC 6819. So it's not replacing them, it's adding a glow-up on top 🌸 It takes everything we learned since OAuth 2.0 came out, covers new threats now that OAuth is everywhere, and deprecates some modes that are either less secure or straight-up insecure πŸ’€


Status of This Memo

This memo documents an Internet Best Current Practice.

This document is a product of the Internet Engineering Task Force (IETF). It represents the consensus of the IETF community. It has received public review and has been approved for publication by the Internet Engineering Steering Group (IESG). More info on BCPs is in Section 2 of RFC 7841.

You can check the current status, errata, and how to give feedback at https://www.rfc-editor.org/info/rfc9700. πŸ”₯

Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication. Please read these carefully, because they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. 🫢


Table of Contents 🧚

1.  Introduction
  1.1.  Structure
  1.2.  Conventions and Terminology
2.  Best Practices
  2.1.  Protecting Redirect-Based Flows
    2.1.1.  Authorization Code Grant
    2.1.2.  Implicit Grant
  2.2.  Token Replay Prevention
    2.2.1.  Access Tokens
    2.2.2.  Refresh Tokens
  2.3.  Access Token Privilege Restriction
  2.4.  Resource Owner Password Credentials Grant
  2.5.  Client Authentication
  2.6.  Other Recommendations
3.  The Updated OAuth 2.0 Attacker Model
4.  Attacks and Mitigations
  4.1.  Insufficient Redirection URI Validation
    4.1.1.  Redirect URI Validation Attacks on Authorization Code
            Grant
    4.1.2.  Redirect URI Validation Attacks on Implicit Grant
    4.1.3.  Countermeasures
  4.2.  Credential Leakage via Referer Headers
    4.2.1.  Leakage from the OAuth Client
    4.2.2.  Leakage from the Authorization Server
    4.2.3.  Consequences
    4.2.4.  Countermeasures
  4.3.  Credential Leakage via Browser History
    4.3.1.  Authorization Code in Browser History
    4.3.2.  Access Token in Browser History
  4.4.  Mix-Up Attacks
    4.4.1.  Attack Description
    4.4.2.  Countermeasures
      4.4.2.1.  Mix-Up Defense via Issuer Identification
      4.4.2.2.  Mix-Up Defense via Distinct Redirect URIs
  4.5.  Authorization Code Injection
    4.5.1.  Attack Description
    4.5.2.  Discussion
    4.5.3.  Countermeasures
      4.5.3.1.  PKCE
      4.5.3.2.  Nonce
      4.5.3.3.  Other Solutions
    4.5.4.  Limitations
  4.6.  Access Token Injection
    4.6.1.  Countermeasures
  4.7.  Cross-Site Request Forgery
    4.7.1.  Countermeasures
  4.8.  PKCE Downgrade Attack
    4.8.1.  Attack Description
    4.8.2.  Countermeasures
  4.9.  Access Token Leakage at the Resource Server
    4.9.1.  Access Token Phishing by Counterfeit Resource Server
    4.9.2.  Compromised Resource Server
    4.9.3.  Countermeasures
  4.10. Misuse of Stolen Access Tokens
    4.10.1.  Sender-Constrained Access Tokens
    4.10.2.  Audience-Restricted Access Tokens
    4.10.3.  Discussion: Preventing Leakage via Metadata
  4.11. Open Redirection
    4.11.1.  Client as Open Redirector
    4.11.2.  Authorization Server as Open Redirector
  4.12. 307 Redirect
  4.13. TLS Terminating Reverse Proxies
  4.14. Refresh Token Protection
    4.14.1.  Discussion
    4.14.2.  Recommendations
  4.15. Client Impersonating Resource Owner
    4.15.1.  Countermeasures
  4.16. Clickjacking
  4.17. Attacks on In-Browser Communication Flows
    4.17.1.  Examples
      4.17.1.1.  Insufficient Limitation of Receiver Origins
      4.17.1.2.  Insufficient URI Validation
      4.17.1.3.  Injection after Insufficient Validation of Sender
              Origin
    4.17.2.  Recommendations
5.  IANA Considerations
6.  Security Considerations
7.  References
  7.1.  Normative References
  7.2.  Informative References
Acknowledgements
Authors' Addresses

1. Introduction 🌈

Ever since [RFC6749] and [RFC6750] dropped, OAuth 2.0 (we'll just say "OAuth" in here) has taken over the market. It's the standard for API protection and the base for federated login with OpenID Connect [OpenID.Core]. OAuth shows up in all kinds of setups, and a few challenges have become super obvious πŸ‘€

  • Implementations keep getting attacked. Attackers are using known implementation weaknesses and anti-patterns (patterns we already know are insecure). [RFC6819] covers most of these threats, but the exploits keep coming, so we need more specific advice, mitigations that are easier to implement, and more defense in depth πŸ›‘οΈ
  • OAuth is being used where the stakes are higher. Think open banking, eHealth, eGovernment, and electronic signatures. Those cases need stricter guidelines and extra protection πŸ’ͺ
  • OAuth is being used in way more dynamic setups than anyone planned for πŸŒ€ Those go beyond the original scope of [RFC6749], [RFC6750], and [RFC6819].

OAuth started out assuming everything was static. The client knew the server URLs at deployment time, and those URLs were the anchor for trust between the parties. Checking whether the client was talking to a legit server relied on TLS server authentication (see Section 4.5.4 of [RFC6819]). As OAuth got popular, that simple model fell apart. In a lot of scenarios, the client and the authorization and resource servers got connected dynamically for a specific deployment. The same client could now reach services from different providers (like standard APIs for email or OpenID Connect), or act as a front end for one tenant in a multi-tenant environment. Extensions like the OAuth 2.0 Dynamic Client Registration Protocol [RFC7591] and OAuth 2.0 Authorization Server Metadata [RFC8414] were built to support dynamic scenarios 🧚

  • Tech changed, and so did the rules. For example, the way browsers handle fragments when redirecting has changed, and that changed the implicit grant's whole security model πŸ™ˆ

This document gives updated security recommendations for all of that. It adds new requirements on top of [RFC6749] and OpenID Connect [OpenID.Core], and deprecates some modes that are less secure or outright insecure πŸ’€ It does not replace the security advice in [RFC6749], [RFC6750], and [RFC6819]. It complements them 🫢

Real talk, not every ecosystem and implementation plays nice with the new requirements, and following these best practices might break interoperability. Still, it is RECOMMENDED that implementers upgrade their implementations and ecosystems as soon as feasible ✨

OAuth 2.1, being developed as [OAUTH-V2.1], will bake in the security recommendations from this document πŸ‘‘

1.1. Structure πŸ—ΊοΈ

Here's the map. Section 2 sums up the most important best practices for every OAuth implementer. Section 3 presents the updated OAuth attacker model. Section 4 is the deep dive: the threats and implementation issues found in the wild (at the time of writing), along with potential countermeasures πŸ’–

1.2. Conventions and Terminology πŸ“–

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be read as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

This spec uses the terms "access token", "authorization endpoint", "authorization grant", "authorization server", "client", "client identifier" (client ID), "protected resource", "refresh token", "resource owner", "resource server", and "token endpoint" as defined by OAuth 2.0 [RFC6749].

An "open redirector" is an endpoint on a web server that forwards a user's browser to an arbitrary URI obtained from a query parameter. πŸ¦‹


2. Best Practices πŸ’…

This section covers the core set of security mechanisms and measures that count as best practices right now (at the time of writing). The nitty-gritty details, including detailed attack descriptions and requirements for less commonly used options, live in Section 4 🌸

(Part 1 of 13. More parts coming, so stay tuned, bestie!) πŸ’œ

2.1. Protecting Redirect-Based Flows ✨

okay bestie, when the authorization server compares a client's redirection URIs against the ones that were pre-registered, it MUST utilize exact string matching πŸ’…. The only exception is port numbers in localhost redirection URIs of native apps (peep Section 4.1.3 for that one πŸ¦‹). This is huge because it helps stop authorization codes and access tokens from leaking πŸ”’, and it can also help catch mix-up attacks (see Section 4.4) πŸ•΅οΈβ€β™€οΈ.

Clients and authorization servers MUST NOT expose URLs that forward the user's browser to arbitrary URIs pulled from a query parameter. These are called open redirectors (see Section 4.11) πŸ™ˆ. Open redirectors can let someone exfiltrate authorization codes and access tokens, and that is so not it πŸ’€.

Clients MUST prevent Cross-Site Request Forgery (CSRF) πŸ›‘οΈ. In this context, CSRF means requests to the redirection endpoint that don't come from the authorization server but from a sneaky malicious third party (see Section 4.4.1.8 of [RFC6819] for the full tea β˜•). Clients that have made sure the authorization server supports Proof Key for Code Exchange (PKCE) [RFC7636] MAY rely on the CSRF protection PKCE provides 🧚. In OpenID Connect flows, the nonce parameter handles the CSRF protection πŸ’œ. Otherwise, one-time use CSRF tokens carried in the state parameter that are securely bound to the user agent MUST be used for CSRF protection (see Section 4.7.1) πŸ”.

When an OAuth client can interact with more than one authorization server, a defense against mix-up attacks (see Section 4.4) is REQUIRED 🚨. To make that happen, clients SHOULD pick one of these πŸ‘‡

  • use the iss parameter as a countermeasure according to [RFC9207], or
  • use an alternative countermeasure based on an iss value in the authorization response (such as the iss claim in the ID Token in [OpenID.Core] or in [OpenID.JARM] responses), processing that value as described in [RFC9207].

If those options aren't available, clients MAY instead use distinct redirection URIs to identify authorization endpoints and token endpoints, as described in Section 4.4.2 🌸.

An authorization server that redirects a request potentially containing user credentials MUST avoid forwarding those user credentials accidentally (see Section 4.12 for details) 🫢.


2.1.1. Authorization Code Grant πŸŽ€

Clients MUST prevent authorization code injection attacks (see Section 4.5) and misuse of authorization codes using one of the following options:

  • Public clients MUST use PKCE [RFC7636] to this end, as motivated in Section 4.5.3.1.
  • For confidential clients, the use of PKCE [RFC7636] is RECOMMENDED, because it gives strong protection against misuse and injection of authorization codes (see Section 4.5.3.1). It also prevents CSRF even when there are strong attackers around (see Section 4.7.1) πŸ‘‘.
  • With additional precautions described in Section 4.5.3.2, confidential OpenID Connect [OpenID.Core] clients MAY use the nonce parameter and the respective Claim in the ID Token instead.

In any case, the PKCE challenge or OpenID Connect nonce MUST be transaction-specific and securely bound to the client and the user agent in which the transaction was started. Authorization servers are encouraged to make a reasonable effort at detecting and preventing the use of constant values for the PKCE challenge or OpenID Connect nonce πŸ”₯.

Note: Although PKCE was designed to protect native apps, this advice applies to all kinds of OAuth clients, including web applications. πŸ’–

When using PKCE, clients SHOULD use PKCE code challenge methods that do not expose the PKCE verifier in the authorization request. Otherwise, attackers that can read the authorization request (cf. Attacker (A4) in Section 3) can break the security PKCE provides 😭. Right now, S256 is the only such method ✨.

Authorization servers MUST support PKCE [RFC7636].

If a client sends a valid code_challenge parameter in the authorization request, the authorization server MUST enforce the correct usage of code_verifier at the token endpoint.

Authorization servers MUST mitigate PKCE downgrade attacks by ensuring that a token request containing a code_verifier parameter is accepted only if a code_challenge parameter was present in the authorization request (see Section 4.8.2 for details) πŸ™…β€β™€οΈ.

Authorization servers MUST provide a way to detect their support for PKCE. It is RECOMMENDED for authorization servers to publish the element code_challenge_methods_supported in their Authorization Server Metadata [RFC8414], containing the supported PKCE challenge methods (which the client can use to detect PKCE support). Authorization servers MAY instead provide a deployment-specific way to ensure or determine PKCE support πŸ§šβ€β™€οΈ.


2.1.2. Implicit Grant πŸ₯€

The implicit grant (response type token) and other response types that cause the authorization server to issue access tokens in the authorization response are vulnerable to access token leakage and access token replay, as described in Sections 4.1, 4.2, 4.3, and 4.6 😩.

Moreover, there's no standardized method for sender-constraining that binds access tokens to a specific client (as recommended in Section 2.2) when the access tokens are issued in the authorization response. That means an attacker can use a leaked or stolen access token at a resource endpoint 🚩.

To avoid these issues, clients SHOULD NOT use the implicit grant (response type token) or other response types that issue access tokens in the authorization response, unless access token injection in the authorization response is prevented and the token leakage vectors mentioned above are mitigated πŸ™…β€β™€οΈ.

Clients SHOULD instead use the response type code (i.e., the authorization code grant type) as specified in Section 2.1.1, or any other response type that causes the authorization server to issue access tokens in the token response, such as the code id_token response type πŸ’…. This lets the authorization server detect replay attempts by attackers and generally shrinks the attack surface, since access tokens aren't exposed in URLs πŸ™Œ. It also lets the authorization server sender-constrain the issued tokens (see Section 2.2) πŸ”.


2.2. Token Replay Prevention πŸ¦‹

2.2.1. Access Tokens

A sender-constrained access token scopes the applicability of an access token to a certain sender. That sender has to prove it knows a certain secret before the recipient (e.g., a resource server) will accept the token πŸ—οΈ.

Authorization and resource servers SHOULD use mechanisms for sender-constraining access tokens, such as mutual TLS for OAuth 2.0 [RFC8705] or OAuth 2.0 Demonstrating Proof of Possession (DPoP) [RFC9449] (see Section 4.10.1), to prevent misuse of stolen and leaked access tokens πŸ›‘οΈβœ¨.

2.2.2. Refresh Tokens

Refresh tokens for public clients MUST be sender-constrained or use refresh token rotation, as described in Section 4.14 πŸ”„. [RFC6749] already says that refresh tokens for confidential clients can only be used by the client they were issued to πŸ’œ.


2.3. Access Token Privilege Restriction πŸ‘‘

The privileges tied to an access token SHOULD be restricted to the minimum required for the particular application or use case 🎯. This stops clients from going beyond the privileges the resource owner authorized, and it stops users from going beyond the privileges their security policy allows. Privilege restrictions also help reduce the damage if an access token leaks 🫧.

Specifically, access tokens SHOULD be audience-restricted to a specific resource server or, if that isn't feasible, to a small set of resource servers πŸŽ€. To make this work, the authorization server associates the access token with certain resource servers, and every resource server is obliged to check, on every request, whether the access token sent with that request was meant for that particular resource server. If it wasn't, the resource server MUST refuse to serve the request πŸ™…β€β™€οΈ. The aud claim defined in [RFC9068] MAY be used to audience-restrict access tokens 🧾. Clients and authorization servers MAY utilize the parameters scope or resource as specified in [RFC6749] and [RFC8707], respectively, to determine which resource server they want to access πŸ”.

Additionally, access tokens SHOULD be restricted to certain resources and actions on resource servers or resources 🌈. To make this work, the authorization server associates the access token with the respective resource and actions, and every resource server is obliged to check, on every request, whether the access token sent with that request was meant for that particular action on that particular resource. If not, the resource server must refuse to serve the request. Clients and authorization servers MAY utilize the parameter scope as specified in [RFC6749], and authorization_details as specified in [RFC9396], to determine those resources and/or actions 🧚.


2.4. Resource Owner Password Credentials Grant πŸ’€

The resource owner password credentials grant [RFC6749] MUST NOT be used. 🚫 This grant type insecurely exposes the resource owner's credentials to the client. Even if the client is totally benign, using this grant grows the attack surface (credentials can leak in more places than just the authorization server) and trains users to type their credentials into places other than the authorization server 😀.

On top of that, the resource owner password credentials grant isn't designed to work with two-factor authentication or with authentication processes that need multiple user interaction steps πŸ™ˆ. Authentication with cryptographic credentials (cf. WebCrypto [W3C.WebCrypto], WebAuthn [W3C.WebAuthn]) may be impossible to implement with this grant type, since it's usually bound to a specific web origin πŸ”.


2.5. Client Authentication 🫢

Authorization servers SHOULD enforce client authentication if, in the particular deployment, it's feasible to set up a process for issuing and registering credentials for clients and to keep those credentials confidential πŸ—οΈ.

It is RECOMMENDED to use asymmetric cryptography for client authentication, such as mutual TLS for OAuth 2.0 [RFC8705] or signed JWTs ("Private Key JWT") in accordance with [RFC7521] and [RFC7523]. The latter is defined in [OpenID.Core] as the client authentication method private_key_jwt). When asymmetric cryptography is used for client authentication, authorization servers don't need to store sensitive symmetric keys, which makes these methods way more robust against key leakage πŸŒŸπŸ’–

2.6. Other Recommendations πŸŽ€

Okay so OAuth Authorization Server Metadata [RFC8414] can seriously level up the security of OAuth deployments πŸ’…:

  • It makes sure security features and other new OAuth features can be switched on automatically by compliant software libraries ✨
  • It cuts down on misconfigurations, like misconfigured endpoint URLs (that might belong to an attacker πŸ’€) or misconfigured security features πŸ™ˆ
  • It can help make rotating cryptographic keys easier and keep your cryptographic agility on point πŸ”‘

So it is RECOMMENDED that authorization servers publish OAuth Authorization Server Metadata according to [RFC8414], and that clients make use of this metadata (when it's available) to configure themselves πŸ’–.

Under the conditions described in Section 4.15.1, authorization servers SHOULD NOT allow clients to influence their client_id or any other claim that could cause confusion with a genuine resource owner 🚫.

It is RECOMMENDED to use end-to-end TLS according to [BCP195] between the client and the resource server. If TLS traffic needs to be terminated at an intermediary, check out Section 4.13 for further security advice πŸ”.

Authorization responses MUST NOT be transmitted over unencrypted network connections. To this end, authorization servers MUST NOT allow redirection URIs that use the http scheme, except for native clients that use loopback interface redirection as described in Section 7.3 of [RFC8252] 🚨.

If the authorization response is sent with in-browser communication techniques like postMessage [WHATWG.postmessage_api] instead of HTTP redirects, both the initiator and receiver of the in-browser message MUST be strictly verified as described in Section 4.17 πŸ‘€.

To support browser-based clients, endpoints that those clients access directly, including the Token Endpoint, Authorization Server Metadata Endpoint, jwks_uri Endpoint, and Dynamic Client Registration Endpoint, MAY support Cross-Origin Resource Sharing (CORS) [WHATWG.CORS]. However, CORS MUST NOT be supported at the authorization endpoint, because the client doesn't access that one directly. Instead, the client redirects the user agent to it 🌸.


3. The Updated OAuth 2.0 Attacker Model 😈

In [RFC6819], there's a threat model that lays out the threats OAuth deployments need protection from. While doing that, [RFC6819] makes some assumptions about attackers and what they can do, which means it implicitly sets up an attacker model. Here we make that attacker model explicit, and we update and expand it to account for the potentially dynamic relationships among multiple parties (as described in Section 1), to include new types of attackers, and to define the attacker model more clearly 🧠.

The goal of this document is to make sure that a resource owner's authorization (given through a user agent) at an authorization server, and the later use of the access token at a resource server, is protected as well as is practically possible. That protection covers at least the following attackers πŸ›‘οΈ.

(A1) Web attackers that can set up and operate an arbitrary number of network endpoints (besides the "honest" ones), including browsers and servers. Web attackers may set up websites that the resource owner visits, operate their own user agents, and take part in the protocol.

In particular, web attackers may operate OAuth clients that are registered at the authorization server, and they may operate their own authorization and resource servers that can be used (in parallel to the "honest" ones) by the resource owner and other resource owners.

We also have to assume that web attackers can lure the user into navigating their browser to any attacker-chosen URI at any time. In practice, this can happen in lots of ways, for example by injecting malicious advertisements into ad networks or by sending legit-looking emails πŸ“§.

Web attackers can use their own user credentials to create new messages, as well as any secrets they've learned before. For example, if a web attacker learns a user's authorization code through a misconfigured redirection URI, the web attacker can then try to redeem that code for an access token 😬.

They cannot, however, read or manipulate messages that aren't aimed at them (for example, messages sent to a URL of an authorization server that isn't under an attacker's control) πŸ™…β€β™€οΈ.

(A2) Network attackers that additionally have full control over the network that protocol participants communicate over. They can eavesdrop on, manipulate, and spoof messages, except when those messages are properly protected by cryptographic methods (e.g., TLS). Network attackers can also block any message they like πŸ•ΈοΈ.

While an example of a web attacker would be a customer of an internet service provider, network attackers could be the internet service provider itself, an attacker on a public (Wi-Fi) network using ARP spoofing, or a state-sponsored attacker with access to internet exchange points, for instance 🌐.

Attackers (A1) and (A2) match the attacker model that was used in formal analysis efforts for OAuth [arXiv.1601.01229]. This is a minimal attacker model. Implementers MUST take into account all possible types of attackers in the environment of their OAuth implementations. For example, [arXiv.1901.11520] uses a very strong attacker model that includes attackers with full control over the token endpoint. That models the effects of a possible misconfiguration of endpoints in the ecosystem, which can be avoided by using authorization server metadata as described in Section 2.6. Because of that, such an attacker isn't listed here πŸ™ƒ.

However, previous attacks on OAuth have shown that the following types of attackers are especially relevant πŸ”.

(A3) Attackers that can read, but not modify, the contents of the authorization response (i.e., the authorization response can leak to an attacker).

Examples of such attacks include open redirector attacks and mix-up attacks (see Section 4.4), where the client is tricked into sending credentials to an attacker-controlled authorization server πŸͺ€.

This also includes attacks that take advantage of:

  • insufficient checking of redirect URIs (see Section 4.1);
  • problems on mobile operating systems, where different apps can register themselves on the same URI; and
  • URLs stored or logged by browsers (history), proxy servers, and operating systems πŸ“±.

(A4) Attackers that can read, but not modify, the contents of the authorization request (i.e., the authorization request can leak, in the same manner as above, to an attacker) πŸ‘€.

(A5) Attackers that can acquire an access token issued by an authorization server. For example, a resource server may be compromised by an attacker, an access token may be sent to an attacker-controlled resource server due to a misconfiguration, or social engineering may be used to get a resource owner to use an attacker-controlled resource server. Also see Section 4.9.2 😱.

(A3), (A4), and (A5) typically occur together with either (A1) or (A2). Attackers can team up to reach a common goal πŸ‘―β€β™€οΈ.

Note that an Attacker (A1) or (A2) can be a resource owner or act as one. For example, such an attacker can use their own browser to replay tokens or authorization codes obtained by any of the attacks described above at the client or resource server πŸ”.

This document focuses on threats resulting from Attackers (A1) to (A5) 🎯.


4. Attacks and Mitigations βš”οΈ

This section gives a detailed description of attacks on OAuth implementations, along with potential countermeasures. Attacks and mitigations already covered in [RFC6819] aren't listed here, except where new recommendations are made πŸ“.

This section also defines additional requirements (beyond those defined in Section 2) for certain cases and protocol options 🧩.

4.1. Insufficient Redirection URI Validation πŸšͺ

Some authorization servers let clients register redirection URI patterns instead of complete redirection URIs. The authorization servers then match the redirection URI parameter value at the authorization endpoint against the registered patterns at runtime. This approach lets clients encode transaction state into additional redirect URI parameters, or register a single pattern for multiple redirection URIs πŸ€”.

This approach turned out to be more complex to implement and more error-prone to manage than exact redirection URI matching. Several successful attacks that exploit flaws in pattern-matching implementations or specific configurations have been observed in the wild (see, e.g., [research.rub2]). Insufficient validation of the redirection URI effectively breaks client identification or authentication (depending on grant and client type), and lets the attacker obtain an authorization code or access token, either 😭:

  • by directly sending the user agent to a URI under the attacker's control, or
  • by exposing the OAuth credentials to an attacker, using an open redirector at the client in conjunction with the way user agents handle URL fragments.

These attacks are shown in detail in the following subsections 🫢.

4.1.1. Redirect URI Validation Attacks on Authorization Code Grant ✨

Okay so, for a client using the grant type code, here's how an attack could go down πŸ‘€

Assume the redirection URL pattern https://*.somesite.example/* is registered for the client with the client ID s6BhdRkqt3 πŸ’–. The intention is to allow any subdomain of somesite.example to be a valid redirection URI for the client, for example, https://app1.somesite.example/redirect. However, a naive implementation on the authorization server might interpret the wildcard * as "any character" and not "any character valid for a domain name". πŸ™ˆ The authorization server, therefore, might permit https://attacker.example/.somesite.example as a redirection URI, although attacker.example is a different domain potentially controlled by a malicious party πŸ’€

The attack can then be conducted as follows:

To begin, the attacker needs to trick the user into opening a tampered URL in their browser that launches a page under the attacker's control, say, https://www.evil.example (see attacker A1 in Section 3) πŸ¦‹

This URL initiates the following authorization request with the client ID of a legitimate client to the authorization endpoint (line breaks for display only):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=9ad67f13
     &redirect_uri=https%3A%2F%2Fattacker.example%2F.somesite.example
     HTTP/1.1
Host: server.somesite.example

The authorization server validates the redirection URI and compares it to the registered redirection URL patterns for the client s6BhdRkqt3. The authorization request is processed and presented to the user πŸŽ€

If the user does not see the redirection URI or does not recognize the attack, the code is issued and immediately sent to the attacker's domain 😭 If an automatic approval of the authorization is enabled (which is not recommended for public clients according to [RFC6749]), the attack can be performed even without user interaction.

If the attacker impersonates a public client, the attacker can exchange the code for tokens at the respective token endpoint πŸ‘‘

This attack will not work as easily for confidential clients, since the code exchange requires authentication with the legitimate client's secret πŸ” However, the attacker can use the legitimate confidential client to redeem the code by performing an authorization code injection attack; see Section 4.5 πŸ“

It is important to note that redirection URI validation vulnerabilities can also exist if the authorization server handles wildcards properly. ⚠️ For example, assume that the client registers the redirection URL pattern https://*.somesite.example/* and the authorization server interprets this as "allow redirection URIs pointing to any host residing in the domain somesite.example". If an attacker manages to establish a host or subdomain in somesite.example, the attacker can impersonate the legitimate client. For example, this could be caused by a subdomain takeover attack [research.udel], where an outdated CNAME record (say, external-service.somesite.example) points to an external DNS name that no longer exists (say, customer-abc.service.example) and can be taken over by an attacker (e.g., by registering as customer-abc with the external service) 🌸

4.1.2. Redirect URI Validation Attacks on Implicit Grant

The attack described above works for the implicit grant as well πŸ’… If the attacker is able to send the authorization response to an attacker-controlled URI, the attacker will directly get access to the fragment carrying the access token 🫢

Additionally, implicit grants (and also other grants when using response_mode=fragment as defined in [OAuth.Responses]) can be subject to a further kind of attack. The attack utilizes the fact that user agents reattach fragments to the destination URL of a redirect if the location header does not contain a fragment (see Section 17.11 of [RFC9110]). The attack described here combines this behavior with the client as an open redirector (see Section 4.11.1) in order to obtain access tokens. This allows circumvention even of very narrow redirection URI patterns, but not of strict URL matching 🧚

Assume the registered URL pattern for client s6BhdRkqt3 is https://client.somesite.example/cb?*, i.e., any parameter is allowed for redirects to https://client.somesite.example/cb. Unfortunately, the client exposes an open redirector 😩 This endpoint supports a parameter redirect_to which takes a target URL and will send the browser to this URL using an HTTP Location header redirect 303.

The attack can now be conducted as follows:

To begin, as above, the attacker needs to trick the user into opening a tampered URL in their browser that launches a page under the attacker's control, say, https://www.evil.example.

Afterwards, the website initiates an authorization request that is very similar to the one in the attack on the code flow. Different to above, it utilizes the open redirector by encoding redirect_to=https://attacker.example into the parameters of the redirection URI, and it uses the response type token (line breaks for display only):

GET /authorize?response_type=token&state=9ad67f13
    &client_id=s6BhdRkqt3
    &redirect_uri=https%3A%2F%2Fclient.somesite.example
     %2Fcb%26redirect_to%253Dhttps%253A%252F
     %252Fattacker.example%252F HTTP/1.1
Host: server.somesite.example

Then, since the redirection URI matches the registered pattern, the authorization server permits the request and sends the resulting access token in a 303 redirect (some response parameters omitted for readability):

HTTP/1.1 303 See Other
Location: https://client.somesite.example/cb?
          redirect_to%3Dhttps%3A%2F%2Fattacker.example%2Fcb
          #access_token=2YotnFZFEjr1zCsicMWpAA&...

At client.somesite.example, the request arrives at the open redirector. The endpoint will read the redirect parameter and will issue an HTTP 303 Location header redirect to the URL https://attacker.example/.

HTTP/1.1 303 See Other
Location: https://attacker.example/

Since the redirector at client.somesite.example does not include a fragment in the Location header, the user agent will reattach the original fragment #access_token=2YotnFZFEjr1zCsicMWpAA&... to the URL and will navigate to the following URL:

https://attacker.example/#access_token=2YotnFZFEjr1z...

The attacker's page at attacker.example can then access the fragment and obtain the access token 😈

4.1.3. Countermeasures

The complexity of implementing and managing pattern matching correctly obviously causes security issues πŸ™ƒ This document therefore advises simplifying the required logic and configuration by using exact redirection URI matching. This means the authorization server MUST ensure that the two URIs are equal; see Section 6.2.1 of [RFC3986], Simple String Comparison, for details. The only exception is native apps using a localhost URI: In this case, the authorization server MUST allow variable port numbers as described in Section 7.3 of [RFC8252] 🌈

Additional recommendations:

  • Web servers on which redirection URIs are hosted MUST NOT expose open redirectors (see Section 4.11).
  • Browsers reattach URL fragments to Location redirection URLs only if the URL in the Location header does not already contain a fragment. Therefore, servers MAY prevent browsers from reattaching fragments to redirection URLs by attaching an arbitrary fragment identifier, for example #_, to URLs in Location headers.
  • Clients SHOULD use the authorization code response type instead of response types that cause access token issuance at the authorization endpoint. This offers countermeasures against the reuse of leaked credentials through the exchange process with the authorization server and against token replay through sender-constraining of the access tokens.

If the origin and integrity of the authorization request containing the redirection URI can be verified, for example, when using [RFC9101] or [RFC9126] with client authentication, the authorization server MAY trust the redirection URI without further checks πŸ›‘οΈ


4.2. Credential Leakage via Referer Headers

The contents of the authorization request URI or the authorization response URI can unintentionally be disclosed to attackers through the Referer HTTP header (see Section 10.1.3 of [RFC9110]), by leaking from either the authorization server's or the client's website, respectively. Most importantly, authorization codes or state values can be disclosed in this way 😱 Although specified otherwise in Section 10.1.3 of [RFC9110], the same may happen to access tokens conveyed in URI fragments due to browser implementation issues, as illustrated by a (now fixed) issue in the Chromium project [bug.chromium] πŸ₯²

4.2.1. Leakage from the OAuth Client

Leakage from the OAuth client requires that the client, as a result of a successful authorization request, renders a page that

  • contains links to other pages under the attacker's control and a user clicks on such a link, or
  • includes third-party content (advertisements in iframes, images, etc.), for example, if the page contains user-generated content (blog).

As soon as the browser navigates to the attacker's page or loads the third-party content, the attacker receives the authorization response URL and can extract code or state (and potentially access_token) πŸ‘€

4.2.2. Leakage from the Authorization Server

In a similar way, an attacker can learn state from the authorization request if the authorization endpoint at the authorization server contains links or third-party content as above 🫣

4.2.3. Consequences

An attacker that learns a valid code or access_token through a Referer header can perform the attacks as described in Sections 4.1.1, 4.5 and 4.6. If the attacker learns state, the CSRF protection achieved by using state is lost, resulting in CSRF attacks as described in Section 4.4.1.8 of [RFC6819] πŸ’”

4.2.4. Countermeasures πŸ’…

okay so the page that gets rendered after the OAuth authorization response, and the authorization endpoint itself, SHOULD NOT include third-party resources or links to external sites πŸ™…β€β™€οΈ 🫢

here's the rest of the toolkit that makes the attack way less likely to work:

  • Suppress the Referer header by applying an appropriate Referrer Policy [W3C.webappsec-referrer-policy] to the document (either as part of the "referrer" meta attribute or by setting a Referrer-Policy header). For example, the header Referrer-Policy: no-referrer in the response completely suppresses the Referer header in all requests originating from the resulting document. πŸ”’
  • Use authorization code instead of response types causing access token issuance from the authorization endpoint. πŸŽ€
  • Bind the authorization code to a confidential client or PKCE challenge. In this case, the attacker lacks the secret to request the code exchange. πŸ¦‹
  • As described in Section 4.1.2 of [RFC6749], authorization codes MUST be invalidated by the authorization server after their first use at the token endpoint. For example, if an authorization server invalidated the code after the legitimate client redeemed it, the attacker would fail to exchange this code later. πŸ’€

this doesn't fully stop it tho 😭 if the attacker manages to exchange the code for a token before the legitimate client does, the invalidation doesn't save you. so [RFC6749] further recommends that, when an attempt is made to redeem a code twice, the authorization server SHOULD revoke all tokens issued previously based on that code. 🚨

  • The state value SHOULD be invalidated by the client after its first use at the redirection endpoint. If this is implemented, and an attacker receives a token through the Referer header from the client's website, the state was already used, invalidated by the client and cannot be used again by the attacker. (This does not help if the state leaks from the authorization server's website, since then the state has not been used at the redirection endpoint at the client yet.) 🌸
  • Use the form post response mode instead of a redirect for the authorization response (see [OAuth.Post]). πŸ‘‘

4.3. Credential Leakage via Browser History πŸ“

authorization codes and access tokens can end up in the browser's history of visited URLs, and that opens the door to the attacks below πŸ‘€

4.3.1. Authorization Code in Browser History πŸ•΅οΈβ€β™€οΈ

when a browser navigates to client.example/redirection_endpoint?code=abcd as a result of a redirect from a provider's authorization endpoint, the URL including the authorization code may end up in the browser's history. an attacker with access to the device could obtain the code and try to replay it 😬

Countermeasures:

  • Authorization code replay prevention as described in Section 4.4.1.1 of [RFC6819], and Section 4.5.
  • Use the form post response mode instead of redirect for the authorization response (see [OAuth.Post]). ✨

4.3.2. Access Token in Browser History 🧚

an access token may end up in the browser history if a client or a website that already has a token deliberately navigates to a page like provider.com/get_user_profile?access_token=abcdef. [RFC6750] discourages this practice and advises transferring tokens via a header, but in practice websites often pass access tokens in query parameters. ngl, it happens a lot πŸ™ˆ

in the case of implicit grant, a URL like client.example/redirection_endpoint#access_token=abcdef may also end up in the browser history as a result of a redirect from a provider's authorization endpoint.

Countermeasures:

  • Clients MUST NOT pass access tokens in a URI query parameter in the way described in Section 2.3 of [RFC6750]. The authorization code grant or alternative OAuth response modes like the form post response mode [OAuth.Post] can be used to this end. 🚫

4.4. Mix-Up Attacks 🌈

mix-up attacks can happen when an OAuth client talks to two or more authorization servers and at least one of them is controlled by the attacker. this can be the case, for example, if the attacker uses dynamic registration to register the client at their own authorization server, or if an authorization server gets compromised 😱

the goal is to get an authorization code or an access token meant for an uncompromised authorization server. this works by tricking the client into sending those credentials to the compromised authorization server (the attacker) instead of using them at the right endpoint of the uncompromised authorization/resource server. main villain behavior πŸ‘‘

4.4.1. Attack Description 🎬

the description here follows [arXiv.1601.01229], with variants of the attack outlined below.

Preconditions: for this variant of the attack to work, it is assumed that

  • the implicit or authorization code grant is used with multiple authorization servers, one of which is considered "honest" (H-AS) and one of which is operated by the attacker (A-AS), and
  • the client stores the authorization server chosen by the user in a session bound to the user's browser and uses the same redirection URI for each authorization server.

in the following, it is further assumed that the client is registered with H-AS (URI: https://honest.as.example, client ID: 7ZGZldHQ) and with A-AS (URI: https://attacker.example, client ID: 666RVZJTA). URLs shown in the following example are shortened for presentation to include only parameters relevant to the attack. πŸ“

Attack on the authorization code grant:

  1. The user selects to start the grant using A-AS (e.g., by clicking on a button on the client's website). πŸ–±οΈ
  2. The client stores in the user's session that the user selected "A-AS" and redirects the user to A-AS's authorization endpoint with a Location header containing the URL https://attacker.example/authorize?response_type=code&client_id=666RVZJTA. πŸ“
  3. When the user's browser navigates to the attacker's authorization endpoint, the attacker immediately redirects the browser to the authorization endpoint of H-AS. In the authorization request, the attacker replaces the client ID of the client at A-AS with the client's ID at H-AS. Therefore, the browser receives a redirection (303 See Other) with a Location header pointing to https://honest.as.example/authorize?response_type=code&client_id=7ZGZldHQ. 😈
  4. The user authorizes the client to access their resources at H-AS. (Note that a vigilant user might at this point detect that they intended to use A-AS instead of H-AS. The first attack variant listed does not have this limitation.) H-AS issues a code and sends it (via the browser) back to the client. 🫣
  5. Since the client still assumes that the code was issued by A-AS, it will try to redeem the code at A-AS's token endpoint. πŸ€¦β€β™€οΈ
  6. The attacker therefore obtains code and can either exchange the code for an access token (for public clients) or perform an authorization code injection attack as described in Section 4.5. πŸ’₯

Variants:

  • Mix-Up with Interception: This variant works only if the attacker can intercept and manipulate the first request/response pair from a user's browser to the client (in which the user selects a certain authorization server and is then redirected by the client to that authorization server), as in Attacker (A2) (see Section 3). This capability can, for example, be the result of an attacker-in-the-middle attack on the user's connection to the client. In the attack, the user starts the flow with H-AS. The attacker intercepts this request and changes the user's selection to A-AS. The rest of the attack proceeds as in Step 2 and following above. πŸ•ΈοΈ
  • Implicit Grant: In the implicit grant, the attacker receives an access token instead of the code in Step 4. The attacker's authorization server receives the access token when the client makes either a request to the A-AS userinfo endpoint (defined in [OpenID.Core]) or a request to the attacker's resource server (since the client believes it has completed the flow with A-AS). 🎟️
  • Per-AS Redirect URIs: If clients use different redirection URIs for different authorization servers, clients do not store the selected authorization server in the user's session, and authorization servers do not check the redirection URIs properly, attackers can mount an attack called "Cross Social-Network Request Forgery". These attacks have been observed in practice. Refer to [research.jcs_14] for details. 🌐
  • OpenID Connect: Some variants can be used to attack OpenID Connect. In these attacks, the attacker misuses features of the OpenID Connect Discovery [OpenID.Discovery] mechanism or replays access tokens or ID Tokens to conduct a mix-up attack. The attacks are described in detail in Appendix A of [arXiv.1704.08539] and Section 6 of [arXiv.1508.04324v2] ("Malicious Endpoints Attacks"). πŸͺ„

4.4.2. Countermeasures πŸ›‘οΈ

when an OAuth client can only interact with one authorization server, a mix-up defense is not required. in scenarios where an OAuth client interacts with two or more authorization servers, though, clients MUST prevent mix-up attacks. two different methods are discussed below. πŸ™Œ

for both defenses, clients MUST store, for each authorization request, the issuer they sent the authorization request to and bind this information to the user agent. the issuer serves, via the associated metadata, as an abstract identifier for the combination of the authorization endpoint and token endpoint that are to be used in the flow. if an issuer identifier is not available (for example, if neither OAuth Authorization Server Metadata [RFC8414] nor OpenID Connect Discovery [OpenID.Discovery] is used), a different unique identifier for this tuple or the tuple itself can be used instead. for brevity of presentation, such a deployment-specific identifier will be subsumed under the issuer (or issuer identifier) in the following. 🧩

it is important to note that just storing the authorization server URL is not sufficient to identify mix-up attacks. an attacker might declare an uncompromised authorization server's authorization endpoint URL as "their" authorization server URL, but declare a token endpoint under their own control. 🚩

4.4.2.1. Mix-Up Defense via Issuer Identification πŸ”

this defense requires that the authorization server sends its issuer identifier in the authorization response to the client. when receiving the authorization response, the client MUST compare the received issuer identifier to the stored issuer identifier. if there is a mismatch, the client MUST abort the interaction. πŸ›‘

there are different ways this issuer identifier can be transported to the client:

  • The issuer information can be transported, for example, via a separate response parameter iss, defined in [RFC9207].
  • When OpenID Connect is used and an ID Token is returned in the authorization response, the client can evaluate the iss claim in the ID Token.

in both cases, the iss value MUST be evaluated according to [RFC9207]. βœ…

while this defense may require deploying new OAuth features to transport the issuer information, it is a robust and relatively simple defense against mix-up πŸŒˆπŸ’–

4.4.2.2. Mix-Up Defense via Distinct Redirect URIs ✨

Okay so for this defense, clients MUST use a distinct redirection URI for each issuer they interact with πŸ’– one URI per issuer, no sharing allowed.

Clients MUST check that the authorization response came from the correct issuer by comparing the distinct redirection URI for that issuer to the URI where the authorization response was actually received πŸ‘€. If there's a mismatch, the client MUST abort the flow πŸ›‘.

This defense is built on existing OAuth functionality, but it can't work when clients only register once and then use many different issuers (like some open banking schemes do) 🏦. Plus, it's so tightly tied to client registration that it's harder to deploy automatically 😭.

Here's the sneaky part πŸ™ˆ. An attacker might be able to get around this defense by registering a new client with the "honest" authorization server, using the redirect URI the client assigned to the attacker's authorization server. The attacker could then run the attack described above, swapping in the client ID of their newly created client.

This defense SHOULD therefore only be used if other options are not available 🫢.

4.5. Authorization Code Injection πŸ’‰

An attacker who has gotten hold of an authorization code in an authorization response (see Attacker (A3) in Section 3) can try to redeem that code for an access token or otherwise make use of it πŸ•΅οΈβ€β™€οΈ.

If the authorization code was created for a public client, the attacker can send it to the token endpoint of the authorization server and get an access token 🎟️. This attack was described in Section 4.4.1.1 of [RFC6819].

For confidential clients, or in some special situations, the attacker can run an authorization code injection attack instead, as described below πŸ’….

In an authorization code injection attack, the attacker tries to inject a stolen authorization code into the attacker's own session with the client 🧚. The goal is to link the attacker's session at the client to the victim's resources or identity, which gives the attacker at least limited access to the victim's resources πŸ‘‘.

Besides getting around client authentication for confidential clients, other use cases for this attack include:

  • The attacker wants to access certain functions in this particular client. For example, the attacker wants to impersonate their victim in a certain app or on a certain website.
  • The authorization or resource servers are limited to certain networks that the attacker can't access directly.

Outside of these special cases, authorization code injection usually isn't worth it when the code was created for a public client πŸ™„, because sending the code to the token endpoint is a simpler and more powerful attack, as described above.

4.5.1. Attack Description 🎬

The authorization code injection attack works like this:

  1. The attacker obtains an authorization code (see Attacker (A3) in Section 3). For the rest of the attack, only the capabilities of a web attacker (A1) are required.
  2. From the attacker's device, the attacker starts a regular OAuth authorization process with the legitimate client.
  3. In the response of the authorization server to the legitimate client, the attacker replaces the newly created authorization code with the stolen authorization code. Since this response passes through the attacker's device, the attacker can use any tool that can intercept and manipulate the authorization response to do this. The attacker does not need to control the network πŸ₯·.
  4. The legitimate client sends the code to the authorization server's token endpoint, along with the redirect_uri, and the client's client ID and client secret (or other means of client authentication).
  5. The authorization server checks the client secret, whether the code was issued to that particular client, and whether the actual redirection URI matches the redirect_uri parameter (see [RFC6749]).
  6. All checks succeed, and the authorization server issues access and other tokens to the client. The attacker has now linked their session with the legitimate client to the victim's resources and/or identity 😈.

4.5.2. Discussion πŸ—£οΈ

Obviously, the check-in step (Step 5) will fail if the code was issued to another client ID, for example a client set up by the attacker 🚫. The check will also fail if the authorization code was already redeemed by the legitimate user and was one-time use only.

An attempt to inject a code obtained via a manipulated redirection URI should also be detected if the authorization server stored the complete redirection URI used in the authorization request and compares it with the redirect_uri parameter πŸ”.

Section 4.1.3 of [RFC6749] requires the authorization server to

ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request as described in Section 4.1.1, and if included ensure that their values are identical.

In the attack scenario described in Section 4.5.1, the legitimate client would use the correct redirection URI it always uses for authorization requests. But this URI would not match the tampered redirection URI used by the attacker (otherwise, the redirect would not land at the attacker's page). So, the authorization server would detect the attack and refuse to exchange the code πŸ™…β€β™€οΈ.

This check could also detect attempts to inject an authorization code that had been obtained from another instance of the same client on another device, if certain conditions are fulfilled:

  • the redirection URI itself contains a nonce or another kind of one-time use, secret data and
  • the client has bound this data to this particular instance of the client.

But this approach conflicts with the idea of enforcing exact redirect URI matching at the authorization endpoint 😬. Moreover, it has been observed that providers very often ignore the redirect_uri check requirement at this stage, maybe because it doesn't seem security-critical from reading the specification πŸ€·β€β™€οΈ.

Other providers just pattern match the redirect_uri parameter against the registered redirection URI pattern. This saves the authorization server from storing the link between the actual redirect URI and the respective authorization code for every transaction. However, this kind of check obviously doesn't fulfill the intent of the specification, since the tampered redirection URI is never considered. So, any attempt to inject an authorization code obtained using the client_id of a legitimate client, or by using the legitimate client on another device, won't be detected in those deployments πŸ’€.

It is also assumed that the requirements defined in Section 4.1.3 of [RFC6749] increase client implementation complexity, since clients need to store or reconstruct the correct redirection URI for the call to the token endpoint 🧠.

Asymmetric methods for client authentication don't stop this attack, because the legitimate client authenticates at the token endpoint πŸ™ƒ.

This document therefore recommends binding every authorization code to a certain client instance on a certain device (or in a certain user agent) in the context of a certain transaction, using one of the mechanisms described next πŸ’œ.

4.5.3. Countermeasures πŸ›‘οΈ

There are two good technical solutions for binding authorization codes to client instances, as follows πŸŽ€.

4.5.3.1. PKCE πŸ”

The PKCE mechanism specified in [RFC7636] can be used as a countermeasure, even though it was originally designed to secure native apps πŸ“±. When the attacker tries to inject an authorization code, the code_verifier check fails: the client uses its correct verifier, but the code is associated with a code_challenge that doesn't match this verifier ❌.

PKCE doesn't just protect against the authorization code injection attack. It also protects authorization codes created for public clients: PKCE makes sure an attacker can't redeem a stolen authorization code at the token endpoint of the authorization server without knowing the code_verifier πŸ₯³.

4.5.3.2. Nonce 🌸

OpenID Connect's existing nonce parameter can protect against authorization code injection attacks πŸ’«. The nonce value is one-time use and is created by the client. The client is supposed to bind it to the user agent session and send it with the initial request to the OpenID Provider (OP). The OP puts the received nonce value into the ID Token that is issued as part of the code exchange at the token endpoint. If an attacker injects an authorization code into the authorization response, the nonce value in the client session and the nonce value in the ID Token received from the token endpoint won't match, and the attack is detected 🚨. The assumption is that an attacker can't get hold of the user agent state on the victim's device (the device the attacker stole the authorization code from) 🫣.

It's important to note that this countermeasure only works if the client properly checks the nonce parameter in the ID Token obtained from the token endpoint, and doesn't use any issued token until that check has succeeded πŸ“Œ. More precisely, a client protecting itself against code injection using the nonce parameter:

  1. MUST validate the nonce in the ID Token obtained from the token endpoint, even if another ID Token was obtained from the authorization response (e.g., response_type=code+id_token), and
  2. MUST ensure that, unless and until that check succeeds, all tokens (ID Tokens and the access token) are disregarded and not used for any other purpose.

It's important to note that nonce does not protect authorization codes of public clients, because an attacker doesn't need to execute an authorization code injection attack. Instead, an attacker can directly call the token endpoint with the stolen authorization code 😱.

4.5.3.3. Other Solutions 🧩

Other solutions like binding state to the code, sender-constraining the code using cryptographic means, or per-instance client credentials are conceivable, but they lack support and bring new security requirements πŸ€”.

PKCE is the most obvious solution for OAuth clients, since it's available at the time of writing, while nonce is appropriate for OpenID Connect clients 🌈.

4.5.4. Limitations 🚧

An attacker can get around the countermeasures described above if they can modify the nonce or code_challenge values used in the victim's authorization request 😀. The attacker can modify these values to be the same ones the client chose in their own session in Step 2 of the attack above. (This requires that the victim's session with the client begins after the attacker started their session with the client.) If the attacker is then able to capture the authorization code from the victim, the attacker will be able to inject the stolen code in Step 3, even if PKCE or nonce are used 😭.

This attack is complex and requires a close interaction between the attacker and the victim's session 🎭. Still, measures to prevent attackers from reading the contents of the authorization response still need to be taken, as described in Sections 4.1, 4.2, 4.3, 4.4, and 4.11 πŸ”’πŸ’–

4.6. Access Token Injection πŸ’‰

okay so picture this: the attacker has a stolen access token and wants to use it to pose as a user in some client. πŸ™ˆ Their move is to inject that token into a legit client that they don't control, so the client just accepts it. bestie, this is so sneaky.

The way it goes down: the attacker starts an OAuth flow with the client using the implicit grant, then tampers with the authorization response. They either swap in the access token the authorization server issued, or they straight up make up a fake authorization server response that carries the leaked token. The response still has the state value the client generated for this exact transaction, so the client doesn't think it's a CSRF attack. It just goes "ooh, valid!" and uses the injected token. 😭

4.6.1. Countermeasures

Here's the tea: there's no way to detect this kind of injection in pure-OAuth flows, because the token gets issued without any binding to the transaction or the particular user agent. πŸ’€

OpenID Connect is the glow-up 🌸. The authorization response also carries an ID Token with an at_hash claim, so the attacker has to replace the access token and the ID Token. They can't forge the ID Token, since it's signed or encrypted with authentication. They also can't slip in a leaked ID Token that matches the stolen access token, because the nonce claim in that leaked ID Token will (with a very high probability) be a different value from the one expected in the authorization response. Not today, attacker!! πŸ’…

Heads up: further protection, like sender-constrained access tokens, is still needed to stop attackers from using the access token directly at the resource endpoint.

The recommendations in Section 2.1.2 come from all of this. ✨

4.7. Cross-Site Request Forgery πŸŽ€

An attacker might try to inject a request into the legit client's redirection URI on the victim's device. The goal is to make the client go access resources under the attacker's control. This is a variant of an attack known as Cross-Site Request Forgery (CSRF). πŸ™ˆ

4.7.1. Countermeasures

The long-established fix is for clients to pass a random value, also known as a CSRF Token, in the state parameter. That's what links the request to the redirection URI with the user agent session, as described earlier. You can read the full details in Section 5.3.5 of [RFC6819]. PKCE or the OpenID Connect nonce value gives the same protection. 🫢

If you're using PKCE instead of state or nonce for CSRF protection, there are a few things you need to know:

  • Clients MUST ensure that the authorization server supports PKCE before using PKCE for CSRF protection. If an authorization server does not support PKCE, state or nonce MUST be used for CSRF protection.
  • If state is used for carrying application state, and the integrity of its contents is a concern, clients MUST protect state against tampering and swapping. This can be done by binding the contents of state to the browser session and/or by signing/encrypting state values. One example is discussed in the expired Internet-Draft [JWT-ENCODED-STATE]. ✨

So the authorization server MUST provide a way to detect its support for PKCE. Using Authorization Server Metadata according to [RFC8414] is RECOMMENDED, but authorization servers MAY instead provide a deployment-specific way to make sure or figure out PKCE support. πŸ‘‘

PKCE gives robust protection against CSRF attacks even when the attacker can read the authorization response (see Attacker (A3) in Section 3). When state is used, or an ID Token comes back in the authorization response (for example response_type=code+id_token), the attacker either learns the state value and replays it into the forged authorization response, or pulls the nonce out of the ID Token and uses it in a new request to the authorization server to mint an ID Token with that same nonce. That new ID Token can then be used for the CSRF attack. Wild, right? 😭

4.8. PKCE Downgrade Attack πŸ¦‹

An authorization server that supports PKCE but doesn't make it mandatory for all flows can be vulnerable to a PKCE downgrade attack. πŸ™ˆ

There are two prerequisites. First, the authorization request has an attacker-controllable flag that turns PKCE on or off for that flow. The presence or absence of the code_challenge parameter is perfect for this, so the authorization server turns on and enforces PKCE if the parameter is there, but doesn't enforce PKCE if it's missing. Second, the client either isn't using state at all (maybe because it relies on PKCE for CSRF prevention) or isn't checking state correctly. πŸ’…

Roughly speaking, this is a variant of a CSRF attack. The attacker gets the same result as in the attack from Section 4.7: they inject an authorization code (and with it, an access token) that's bound to the attacker's resources into a session between the victim and the client. 🧚

4.8.1. Attack Description

  1. The user has started an OAuth session using some client at an authorization server. In the authorization request, the client has set the parameter code_challenge=hash(abc) as the PKCE code challenge (with the hash function and parameter encoding defined in [RFC7636]). The client is now waiting for the authorization response from the user's browser.
  2. To pull this off, the attacker uses their own device to start an authorization flow with the targeted client. The client now uses another PKCE code challenge, say code_challenge=hash(xyz), in the authorization request. The attacker intercepts the request and removes the entire code_challenge parameter. Since this step happens on the attacker's device, the attacker has full access to the request contents, for example through browser debug tools. πŸ•΅οΈβ€β™€οΈ
  3. If the authorization server allows flows without PKCE, it will create a code that isn't bound to any PKCE code challenge.
  4. The attacker now redirects the user's browser to an authorization response URL that contains the code for the attacker's session with the authorization server.
  5. The user's browser sends the authorization code to the client, which now tries to redeem the code for an access token at the authorization server. The client sends code_verifier=abc as the PKCE code verifier in the token request.
  6. Since the authorization server sees that this code isn't bound to any PKCE code challenge, it doesn't check the presence or contents of the code_verifier parameter. It issues an access token (which belongs to the attacker's resource) to the client under the user's control. 😱

4.8.2. Countermeasures

Using state properly would prevent this attack. However, practice has shown that many OAuth clients don't use or check state properly. πŸ™ƒ

So authorization servers MUST mitigate this attack. πŸ›‘οΈ

Here's the clever part: from the authorization server's point of view, in the attack above, a code_verifier parameter arrives at the token endpoint even though no code_challenge parameter was in the authorization request for the OAuth flow that issued the authorization code. That fact can be used to stop the attack. [RFC7636] already says that:

  • an authorization server that supports PKCE MUST check whether a code challenge is contained in the authorization request and bind this information to the code that is issued; and
  • when a code arrives at the token endpoint, and there was a code_challenge in the authorization request for which this code was issued, there must be a valid code_verifier in the token request.

On top of that, to prevent PKCE downgrade attacks, the authorization server MUST ensure that if there was no code_challenge in the authorization request, a request to the token endpoint containing a code_verifier is rejected. 🚫

Authorization servers that mandate PKCE (in general or for particular clients) implicitly implement this security measure. πŸ’œ

4.9. Access Token Leakage at the Resource Server πŸ’§

Access tokens can leak from a resource server under certain circumstances. Yikes. 😬

4.9.1. Access Token Phishing by Counterfeit Resource Server 🎣

An attacker may set up their own resource server and trick a client into sending it access tokens that are valid for other resource servers (see Attackers (A1) and (A5) in Section 3). If the client sends a valid access token to this counterfeit resource server, the attacker can then use that token to access other services on behalf of the resource owner. πŸ™ˆ

This attack assumes the client isn't bound to one specific resource server (and its URL) at development time, but client instances get the resource server URL at runtime. This kind of late binding is typical when the client uses a service implementing a standardized API (for example for email, calendaring, eHealth, or open banking) and when the client is configured by a user or administrator. πŸ“¬

4.9.2. Compromised Resource Server 🧨

An attacker may compromise a resource server to get access to the resources of that deployment. The compromise can range from partial access to the system, like its log files, all the way to full control of the server. With full control, all controls can be circumvented and all resources can be accessed. The attacker could also get other access tokens held on the compromised system that might be valid for other resource servers. 😱

Preventing server breaches by hardening and monitoring server systems is considered standard operational procedure, so it's out of the scope of this document. Section 4.9 focuses on the impact of OAuth-related breaches and on replaying captured access tokens. πŸ”

4.9.3. Countermeasures πŸ›‘οΈ

The following measures should be taken into account by implementers to cope with access token replay by malicious actors:

  • Sender-constrained access tokens, as described in Section 4.10.1, SHOULD be used to stop the attacker from replaying the access tokens on other resource servers. If an attacker only has partial access to the compromised system, like read-only access to web server logs, sender-constrained access tokens may also prevent replay on the compromised system.
  • Audience restriction, as described in Section 4.10.2, SHOULD be used to prevent replay of captured access tokens on other resource servers.
  • The resource server MUST treat access tokens like other sensitive secrets and not store or transfer them in plaintext. 🀫

The first and second recommendations also apply to other scenarios where access tokens leak (see Attacker (A5) in Section 3). ✨

4.10. Misuse of Stolen Access Tokens πŸ”

Okay so access tokens can get snatched by attackers in a bunch of ways, like the attacks we already went over in Sections 4.1, 4.2, 4.3, 4.4, and 4.9. Some of these have specific fixes in their own sections, but literally sometimes those fixes aren't enough or nobody actually implements them right 😭. So authorization servers SHOULD make sure access tokens are sender-constrained and audience-restricted, which is what the rest of this section is about. Heads up though, architecture and performance stuff might stop some deployments from doing this, and that's valid 🫢.

4.10.1. Sender-Constrained Access Tokens πŸŽ€

Sender-constrained access tokens are giving "only you can use this, bestie" energy. The token's usage is scoped to a certain sender, and that sender has to prove it knows a certain secret before the resource server will accept the token πŸ’….

Here's the typical flow ✨:

  1. The authorization server attaches some data to the access token that ties it to a specific client. That binding can use the client's identity, but most of the time the authorization server uses key material (or data derived from that key material) that the client knows πŸ”‘.
  2. That key material has to get distributed somehow πŸ“¦. Either the key material already exists before the authorization server makes the binding, or the authorization server creates ephemeral keys. How preexisting key material gets distributed depends on the approach. X.509 certificates, for example, get distributed explicitly during enrollment. Or the key material gets created and distributed at the TLS layer, where it might just happen automatically when a TLS connection gets set up 🌸.
  3. The resource server MUST implement the actual proof-of-possession check πŸ™ˆ. This is usually done at the application level and often tied to specific material from the transport layer (like TLS). The resource server also MUST make sure a replay of the proof of possession is not possible, because nobody gets to copy-paste the homework πŸ’€.

Two methods for sender-constrained access tokens using proof of possession have been defined by the OAuth working group, and they're actually used in the real world πŸ¦‹:

  • "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens" [RFC8705]: This one lets mutual TLS handle both client authentication and sender-constrained access tokens πŸ”₯. For the sender-constrained part, the client gets identified toward the resource server by the fingerprint of its public key. When the authorization server processes an access token request, it grabs the client's public key from the TLS stack and attaches its fingerprint to the access tokens. The resource server does the same thing: it grabs the public key from the TLS stack and compares its fingerprint with the one tied to the access token 🫢.
  • "OAuth 2.0 Demonstrating Proof of Possession (DPoP)" [RFC9449]: DPoP is an application-level mechanism for sender-constraining access and refresh tokens 🌈. It uses proof of possession based on a public/private key pair and application-level signing ✍️. DPoP works with public clients, and for confidential clients it can be combined with any client authentication method πŸ‘‘.

Now the important caveat 🚨. Sender-constrained tokens fall apart when an attacker gets both the token and the key material 😭. That happens in particular with corrupted client software and cross-site scripting attacks, especially when the client runs in the browser πŸ’€. But if the key material is protected in a hardware or software security module, or it's only indirectly accessible (like inside a TLS stack), sender-constrained tokens at least protect against misuse of the token when the client is offline 🧚. That means when the security module or interface isn't available to the attacker. This applies to access tokens and to refresh tokens too (see Section 4.14) πŸ“.

4.10.2. Audience-Restricted Access Tokens πŸ¦‹

Audience restriction basically pins access tokens to one particular resource server πŸ“Œ. The authorization server links the access token to that resource server, and the resource server is then supposed to check the intended audience. If the access token fails that audience check, the resource server refuses to serve the request πŸ™…β€β™€οΈ.

In general, audience restriction limits how much damage token leakage can do πŸ’–. With a counterfeit resource server, it may (as described below) also stop the phished access token from being abused at the legitimate resource server πŸ•΅οΈβ€β™€οΈ.

The audience can be a logical name or a physical address (like a URL) πŸ—ΊοΈ. To prevent phishing, it's necessary to use the actual URL the client will send requests to. In the phishing case, that URL points to the counterfeit resource server 🎣. If the attacker tries to use the access token at the legitimate resource server (which has a different URL), the resource server will spot the mismatch (wrong audience) and refuse to serve the request 🚫.

In deployments where the authorization server knows the URLs of all resource servers, the authorization server may just refuse to issue access tokens for unknown resource server URLs πŸ™….

For this to work, the client needs to tell the authorization server which resource server it intends to use. The mechanism in [RFC8707] can be used for this, or the information can be encoded in the scope value (Section 3.3 of [RFC6749]) πŸ“.

Instead of the URL, you can also use the fingerprint of the resource server's X.509 certificate as the audience value πŸ”. This variant would also let you detect an attempt to spoof the legitimate resource server's URL using a valid TLS certificate from a different CA πŸ‘€. It might also count as a privacy benefit to hide the resource server URL from the authorization server 🫣.

Audience restriction might seem easier to use, since it doesn't need any cryptography on the client side πŸ₯°. Still, since every access token is bound to a specific resource server, the client also needs to get a resource server-specific access token when it accesses several resource servers 🧩. (Resource indicators, as specified in [RFC8707], can help with that.) [TOKEN-BINDING] had the same property, since different token-binding IDs had to be associated with the access token πŸ™ƒ. Using mutual TLS for OAuth 2.0 [RFC8705], on the other hand, lets a client use the access token at multiple resource servers πŸ™Œ.

It should be noted that audience restrictions (or, more generally, the client telling the authorization server where it wants to use the access token) have extra benefits beyond stopping token leakage πŸ’«. They let the authorization server mint a different access token whose format and content are specifically made for each server. That's a HUGE functional and privacy win in deployments using structured access tokens πŸ†.

4.10.3. Discussion: Preventing Leakage via Metadata πŸ—‚οΈ

An authorization server could give the client extra info about where it's safe to use its access tokens πŸ€”. This approach, and why it's not recommended, is what we're talking about here πŸ‘‡.

In the simplest form, the authorization server would publish a list of its known resource servers. Here's an example using a non-standard Authorization Server Metadata parameter called resource_servers:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "issuer":"https://server.somesite.example",
  "authorization_endpoint":
    "https://server.somesite.example/authorize",
  "resource_servers":[
    "email.somesite.example",
    "storage.somesite.example",
    "video.somesite.example"
  ]
  ...
}

The authorization server could also return the URL(s) an access token is good for in the token response. Here's the example, with the non-standard return parameter access_token_resource_server πŸ“¬:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "access_token_resource_server":
    "https://hostedresource.somesite.example/path1",
...
}

This mitigation strategy would rely on the client to enforce the security policy and only send access tokens to legitimate destinations πŸ™. OAuth-related security research (see, for example, [research.ubc] and [research.cmu]) shows that a large portion of client implementations don't implement security controls properly, or fail at them, like state checks 😬. So relying on clients to stop access token phishing is likely to fail too πŸ’”. Plus, given how many clients there are compared to authorization and resource servers, it's considered the more viable approach to move as much security logic as possible onto those servers πŸ–₯️. Clearly, the client has to contribute to overall security 🀝. But there are alternative countermeasures, as described in Sections 4.10.1 and 4.10.2, that strike a better balance between the parties involved βš–οΈ.

4.11. Open Redirection πŸšͺ

The following attacks can happen when an authorization server or client has an open redirector πŸ•³οΈ. These endpoints sometimes get built to show a message before a user gets redirected to an external website, or to send users back to a URL they were trying to visit before a login prompt interrupted them πŸ”.

4.11.1. Client as Open Redirector 🎯

Clients MUST NOT expose open redirectors 🚫. Attackers may use open redirectors to produce URLs that point to the client, then use them to exfiltrate authorization codes and access tokens, as described in Section 4.1.2 😱. Another abuse case is producing URLs that appear to point to the client. That can trick users into trusting the URL and following it in their browser, and it can be abused for phishing 🎣.

To prevent open redirection, clients SHOULD only redirect if the target URLs are allowed, or if the origin and integrity of a request can be authenticated πŸ›‘οΈ. Countermeasures against open redirection are described by OWASP [owasp.redir] πŸ“š.

4.11.2. Authorization Server as Open Redirector πŸ¦‹

Okay so the authorization server gets the same treatment as clients πŸ‘€. Attackers can try to ride on a user's trust in the authorization server (and especially its URL) to pull off phishing attacks 😭. Authorization servers redirect people to other websites (the clients) all the time, so they gotta do it safely πŸ’….

Section 4.1.2.1 of [RFC6749] already blocks open redirects by saying that the authorization server MUST NOT automatically redirect the user agent when there's an invalid combo of client_id and redirect_uri. Nice try, but not enough πŸ™ƒ.

Here's the plot twist: an attacker can also abuse a redirection URI that's totally, correctly registered πŸ•΅οΈβ€β™€οΈ. For example, they could register a client through dynamic client registration [RFC7591] and then try one of these:

  1. Send a messed-up authorization request on purpose, like with an invalid scope value, so the authorization server sends the user agent to its phishing site anyway πŸ’€.
  2. Send a valid-looking authorization request with the attacker's client_id and redirect_uri. The user logs in, and then the authorization server asks for consent. If the user notices something fishy and declines, the authorization server still redirects them to the phishing site 😀. So the user agent lands there no matter what the user picks.
  3. Send a valid silent authentication request (prompt=none) with the attacker's client_id and redirect_uri. Then the authorization server automatically redirects the user agent to the phishing site 🫠.

The authorization server MUST take precautions against these threats πŸ›‘οΈ. It MUST always authenticate the user first and, except for the silent authentication use case, prompt for credentials when needed, before redirecting the user. Based on its risk assessment, the server has to decide whether or not it can trust the redirection URI. It could look at URI analytics, done in-house or by some external service, to judge how credible and trustworthy the content behind the URI is. It could also check where the redirection URI came from and look at any other client data πŸ”.

The authorization server SHOULD only automatically redirect the user agent if it trusts the redirection URI πŸ’–. If the URI isn't trusted, the authorization server MAY tell the user and leave the decision to them 🫢.


4.12. 307 Redirect 🌸

At the authorization endpoint, the usual flow is that the authorization server prompts the user to enter their credentials in a form. That form gets submitted (using the HTTP POST method) back to the authorization server. The server checks the credentials and, if they're good, redirects the user agent to the client's redirection endpoint ✨.

[RFC6749] uses the HTTP status code 302 (Found) for this, but "any other method available via the user-agent to accomplish this redirection is allowed". Here's where it gets messy 😬. When the status code 307 is used for the redirect instead, the user agent will send the user's credentials via HTTP POST to the client.

So the client gets the sensitive credentials 😱. If the client is shady, it can use them to impersonate the user at the authorization server πŸ’€.

This behavior might be unexpected for developers, but it's defined in Section 15.4.8 of [RFC9110] πŸ™ˆ. The 307 status code does not require the user agent to rewrite the POST request into a GET request, which would drop the form data in the POST request body. Rude 😀.

In the HTTP standard [RFC9110], only the status code 303 unambiguously enforces rewriting the HTTP POST request to an HTTP GET request. For all other status codes, including the popular 302, user agents can opt not to rewrite POST to GET requests. That means the user's credentials could get revealed to the client. (In practice, though, most user agents will only show this behavior for 307 redirects πŸ‘€.)

So authorization servers that redirect a request that might contain the user's credentials MUST NOT use the HTTP 307 status code for redirection 🚫. If an HTTP redirection (and not, for example, JavaScript) is used for such a request, the authorization server SHOULD use HTTP status code 303 (See Other) πŸŽ€.


4.13. TLS Terminating Reverse Proxies 🧚

A super common way to deploy HTTP apps is to hide the application server behind a reverse proxy. The proxy terminates the TLS connection and then dispatches the incoming requests to the right application server nodes πŸͺ„.

This section covers some attack angles of this setup that matter for OAuth, plus recommendations for security controls πŸ”.

Sometimes the reverse proxy has to pass security-related data to the upstream application servers so they can process it. Examples include the IP address of the request originator, token-binding IDs, and authenticated TLS client certificates. This data usually gets passed in HTTP headers added to the upstream request. The headers are often custom and application-specific, but standardized header fields for client certificates and client certificate chains are defined in [RFC9440] πŸ“œ.

Now here's where it gets sus 😳. If the reverse proxy passes through any header sent from the outside, an attacker could send faked header values through the proxy straight to the application server. That's a way to get around security controls πŸ™ƒ. For example, it's standard practice for reverse proxies to accept X-Forwarded-For headers and just add the origin of the inbound request, making it a list. Depending on the logic in the application server, the attacker could simply add an allowed IP address to the header and make the protection totally useless πŸ’€.

A reverse proxy MUST therefore sanitize any inbound requests to make sure the authenticity and integrity of all header values relevant for the security of the application servers are intact 🧹.

And if an attacker got access to the internal network between the proxy and the application server, they could also try to get around the security controls in place 😱. So it's essential to make sure the communicating entities are authentic 🫑. On top of that, the communication link between the reverse proxy and the application server MUST be protected against eavesdropping, injection, and replay of messages πŸ”’.


4.14. Refresh Token Protection πŸ’Ž

Refresh tokens are a convenient, user-friendly way to get new access tokens πŸ₯°. They also make OAuth more secure, because they let the authorization server issue access tokens with a short lifetime and reduced scope. That shrinks the potential damage if an access token leaks πŸ›‘οΈ.

4.14.1. Discussion πŸ”

Refresh tokens are a juicy target for attackers 🍯. They represent the full scope of access granted to a certain client, and they aren't constrained to a specific resource. So if an attacker can exfiltrate a refresh token and successfully replay it, they can mint access tokens and use them to access resource servers on behalf of the resource owner. Big yikes 😭.

[RFC6749] already gives robust baseline protection by requiring:

  • confidentiality of the refresh tokens in transit and storage,
  • refresh tokens to be transmitted over TLS-protected connections between the authorization server and the client,
  • the authorization server to maintain and check the binding of a refresh token to a certain client, and authentication of this client during token refresh if possible, and
  • refresh tokens that cannot be generated, modified, or guessed.

[RFC6749] also lays the foundation for further (implementation-specific) security measures 🧱. Think refresh token expiration and revocation, plus refresh token rotation. It does this by defining the respective error codes and response behaviors.

This specification gives recommendations that go beyond the scope of [RFC6749], along with some clarifications πŸ“.

4.14.2. Recommendations ✨

Authorization servers MUST determine, based on a risk assessment, whether to issue refresh tokens to a certain client 🧠. If the authorization server decides not to issue refresh tokens, the client MAY get a new access token by using other grant types, like the authorization code grant type. In that case, the authorization server may use cookies and persistent grants to make the user experience smoother πŸͺ.

If refresh tokens are issued, they MUST be bound to the scope and resource servers the resource owner consented to 🎯. This stops the legitimate client from escalating its own privileges, and it shrinks the impact of refresh token leakage.

For confidential clients, [RFC6749] already requires that refresh tokens can only be used by the client they were issued for πŸ”.

Authorization servers MUST use one of these methods to detect refresh token replay by malicious actors for public clients πŸ•΅οΈ:

  • Sender-constrained refresh tokens: the authorization server cryptographically binds the refresh token to a certain client instance, for example by using [RFC8705] or [RFC9449] πŸ”—.
  • Refresh token rotation: the authorization server issues a new refresh token with every access token refresh response. The previous refresh token gets invalidated, but the authorization server keeps information about the relationship 🧾. If a refresh token gets compromised and then used by both the attacker and the legitimate client, one of them will present an invalidated refresh token. That tips the authorization server off to the breach. The authorization server can't tell which party sent the invalid refresh token, but it will revoke the active refresh token. This stops the attack, but the cost is forcing the legitimate client to get a fresh authorization grant 😩.

Implementation note: the grant a refresh token belongs to may be encoded into the refresh token itself 🧩. That can help an authorization server efficiently figure out which grant a refresh token belongs to and, by extension, all the refresh tokens that need revoking. In this case, authorization servers MUST ensure the integrity of the refresh token value, for example by using signatures ✍️.

Authorization servers MAY revoke refresh tokens automatically in case of a security event, such as:

  • a password change, or
  • a logout at the authorization server πŸšͺ.

Refresh tokens SHOULD expire if the client has been inactive for some time, meaning the refresh token hasn't been used to get fresh access tokens for a while ⏳. The expiration time is up to the authorization server. It might be a global value, or it might be based on the client policy or on the grant associated with the refresh token (and its sensitivity) πŸ’….

4.15. Client Impersonating Resource Owner 🎭

Okay so, resource servers can make access control decisions based on the identity of the resource owner an access token was issued for, or based on the identity of a client in the client credentials grant. πŸ’… For example, [RFC9068] (JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens) describes an access token data structure with a sub claim, defined like this:

In cases of access tokens obtained through grants where a resource
owner is involved, such as the authorization code grant, the value
of "sub" SHOULD correspond to the subject identifier of the
resource owner.  In cases of access tokens obtained through grants
where no resource owner is involved, such as the client
credentials grant, the value of "sub" SHOULD correspond to an
identifier the authorization server uses to indicate the client
application.

Now here's the tea πŸ«–. If both options are possible, a resource server might mistake a client's identity for a resource owner's identity. Say a client gets to pick its own client_id during registration. A malicious client could set it to a value that identifies a resource owner (for example, a sub value if OpenID Connect is in use) 😈. If the resource server can't properly tell apart access tokens obtained with the resource owner involved from ones obtained without, the client might accidentally get into resources that belong to the resource owner. Not okay!! 😭

This attack doesn't only affect implementations using [RFC9068]. Bespoke, home-grown solutions can get hit by it too πŸ™ˆ.

4.15.1. Countermeasures πŸ›‘οΈ

Authorization servers SHOULD NOT allow clients to influence their client_id or any other claim that could cause confusion with a genuine resource owner, if a common namespace for client IDs and user identifiers exists. The sub claim example from [RFC9068] shown in Section 4.15 above is one of those. Where this cannot be avoided, authorization servers MUST provide other means for the resource server to distinguish between the two types of access tokens πŸ”‘.

4.16. Clickjacking πŸ–±οΈ

As described in Section 4.4.1.9 of [RFC6819], the authorization request is susceptible to clickjacking attacks, also called user interface redressing. 😬 In this kind of attack, an attacker embeds the authorization endpoint user interface in an innocent-looking context. A user who thinks they're interacting with that context (say, by clicking buttons) ends up interacting with the authorization endpoint user interface instead. The opposite can happen too: a user who thinks they're interacting with the authorization endpoint might accidentally type a password into an attacker-provided input field that's overlaid on the original user interface. Clickjacking attacks can be designed so users can barely notice anything, for example by using almost invisible iframes overlaid on top of other elements πŸ‘».

An attacker can use this vector to get the user's authentication credentials, change the scope of access granted to the client, and potentially access the user's resources. Scary stuff πŸ’€.

Authorization servers MUST prevent clickjacking attacks. Multiple countermeasures are described in [RFC6819], including the use of the X-Frame-Options HTTP response header field and frame-busting JavaScript. On top of those, authorization servers SHOULD also use Content Security Policy (CSP) level 2 [W3C.CSP-2] or greater 🌸.

To be effective, CSP must be used on the authorization endpoint and, if applicable, other endpoints used to authenticate the user and authorize the client (e.g., the device authorization endpoint, login pages, error pages, etc.). This stops framing by unauthorized origins in user agents that support CSP πŸ™Œ. The client MAY permit being framed by some other origin than the one used in its redirection endpoint. For this reason, authorization servers SHOULD allow administrators to configure allowed origins for particular clients, and/or for clients to register these dynamically πŸ’œ.

Using CSP lets authorization servers specify multiple origins in a single response header field, and constrain them using flexible patterns (see [W3C.CSP-2] for details). CSP level 2 gives a robust mechanism for protecting against clickjacking: policies that restrict the origin of frames (using frame-ancestors), together with policies that restrict which scripts are allowed to execute on an HTML page (using script-src). ✨ A non-normative example of such a policy is shown in the following listing:

HTTP/1.1 200 OK
Content-Security-Policy: frame-ancestors https://ext.example.org:8000
Content-Security-Policy: script-src 'self'
X-Frame-Options: ALLOW-FROM https://ext.example.org:8000
...

Because some user agents don't support [W3C.CSP-2], this technique SHOULD be combined with others, including those described in [RFC6819], unless such legacy user agents are explicitly unsupported by the authorization server. Even in such cases, additional countermeasures SHOULD still be employed 🧁.

4.17. Attacks on In-Browser Communication Flows πŸͺŸ

If the authorization response is sent with in-browser communication techniques like postMessage [WHATWG.postmessage_api] instead of HTTP redirects, messages may accidentally get sent to malicious origins, or get injected from malicious origins. Not the vibe πŸ™…β€β™€οΈ.

4.17.1. Examples πŸ§ͺ

The following non-normative pseudocode examples of attacks using in-browser communication are described in [research.rub] πŸ”¬.

4.17.1.1. Insufficient Limitation of Receiver Origins 🎯

When sending the authorization response or token response via postMessage, the authorization server sends the response to the wildcard origin "*" instead of the client's origin. If the window receiving the response is controlled by an attacker, the attacker can read the response πŸ‘€.

window.opener.postMessage(
  {
    code: "ABC",
    state: "123"
  },
  "*" // any website in the opener window can receive the message
)

4.17.1.2. Insufficient URI Validation 🚩

When sending the authorization response or token response via postMessage, the authorization server may not check the receiver origin against the redirection URI. Instead, it may, for example, send the response to an origin provided by an attacker. This is analogous to the attack described in Section 4.1 πŸ’….

window.opener.postMessage(
  {
    code: "ABC",
    state: "123"
  },
  "https://attacker.example" // attacker-provided value
)

4.17.1.3. Injection after Insufficient Validation of Sender Origin πŸ’‰

A client that expects the authorization response or token response via postMessage may not validate the sender origin of the message. This may allow an attacker to inject an authorization response or token response into the client 😱.

In the case of a maliciously injected authorization response, the attack is a variant of the CSRF attacks described in Section 4.7. The countermeasures described in Section 4.7 apply to this attack as well πŸ”.

In the case of a maliciously injected token response, sender-constrained access tokens as described in Section 4.10.1 may prevent the attack under some circumstances. However, additional countermeasures as described in Section 4.17.2 are generally required 🧩.

4.17.2. Recommendations ⭐

When comparing client receiver origins against pre-registered origins, authorization servers MUST utilize exact string matching as described in Section 4.1.3. Authorization servers MUST send postMessages to trusted client receiver origins, as shown in the following non-normative example πŸŽ€:

window.opener.postMessage(
  {
    code: "ABC",
    state: "123"
  },
  "https://client.example" // use explicit client origin
)

Wildcard origins like "*" in postMessage MUST NOT be used, because attackers can use them to leak a victim's in-browser message to malicious origins 🚫. Both measures help prevent leakage of authorization codes and access tokens (see Section 4.1) πŸ”.

Clients MUST prevent injection of in-browser messages on the client receiver endpoint. Clients MUST utilize exact string matching to compare the initiator origin of an in-browser message with the authorization server origin, as shown in the following non-normative example 🫢:

window.addEventListener("message", (e) => {
  // validate exact authorization server origin
  if (e.origin === "https://honest.as.example") {
    // process e.data.code and e.data.state
  }
})

Since in-browser communication flows only apply a different communication technique (i.e., postMessage instead of HTTP redirect), all measures protecting the authorization response listed in Section 2.1 MUST be applied equally. No skipping, bestie πŸ¦‹.

5. IANA Considerations πŸ“‹

This document has no IANA actions. Nothing to register, nothing to update, all good ✨.

6. Security Considerations πŸ”

Security considerations are described in Sections 2, 3, and 4. Yes, all the scary, high-stakes stuff lives in those sections, so go reread them with your full attention 😭πŸ”₯.

7. References πŸ“š

7.1. Normative References

Okay so bestie, this is the references list for the normative stuff πŸ“šβœ¨ Normative references are the docs you HAVE to read to actually understand the spec, no cap πŸ’…

  • [BCP195] 🌸 This one is a Best Current Practice, and it's basically a bundle. At the time of writing it contains:

Moriarty, K. and S. Farrell, "Deprecating TLS 1.0 and TLS 1.1", BCP 195, RFC 8996, DOI 10.17487/RFC8996, March 2021, <https://www.rfc-editor.org/info/rfc8996>.

Sheffer, Y., Saint-Andre, P., and T. Fossati, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)", BCP 195, RFC 9325, DOI 10.17487/RFC9325, November 2022, <https://www.rfc-editor.org/info/rfc9325>.

Link for the whole BCP: <https://www.rfc-editor.org/info/bcp195> πŸ”—

7.2. Informative References ✨

okay bestie, this is the extra-credit reading pile πŸ“šπŸ«Ά these are the sources the document points to for background. Here they are, all the way through:

Acknowledgements ✨

Okay bestie, this is the thank-you section, and the squad list is HUGE 🫢 We want to give a massive shoutout to Brock Allen, Annabelle Richard Backman, Dominick Baier, Vittorio Bertocci, Brian Campbell, Bruno Crispo, William Dennis, George Fletcher, Matteo Golinelli, Dick Hardt, Joseph Heenan, Pedram Hosseyni, Phil Hunt, Tommaso Innocenti, Louis Jannett, Jared Jennings, Michael B. Jones, Engin Kirda, Konstantin Lapine, Neil Madden, Christian Mainka, Jim Manico, Nov Matake, Doug McDorman, Karsten Meyer zu Selhausen, Ali Mirheidari, Vladislav Mladenov, Kaan Onarioglu, Aaron Parecki, Michael Peck, Johan Peeters, Nat Sakimura, Guido Schmitz, JΓΆrg Schwenk, Rifaat Shekh-Yusef, Travis Spencer, Petteri Stenius, Tomek Stojecki, David Waite, Tim WΓΌrtele, and Hans Zandbelt for their valuable feedback πŸ’–πŸŒˆ Literally iconic behaviour, the whole group chat is obsessed with you all πŸ˜­πŸ‘‘


Authors' Addresses πŸŽ€

Here are the people who actually wrote this doc, plus where to reach them πŸ’Œ

  • Torsten Lodderstedt SPRIND Email: torsten@lodderstedt.net πŸ¦‹
  • John Bradley Yubico Email: ve7jtb@ve7jtb.com 🌸
  • Andrey Labunets Independent Researcher Email: isciurus@gmail.com 🧚
  • Daniel Fett Authlete Email: mail@danielfett.de πŸ’œ
πŸ“¦ where this page comes from