OAuth 2.0
OAuth security mistakes (RFC 9700)
A plain-English, section-by-section summary of the current OAuth security best practice, with the exact requirement strength of each rule.
RFC 9700, "Best Current Practice for OAuth 2.0 Security" (BCP 240, January 2025), is the document that says how OAuth should be deployed today. It updates RFC 6749, RFC 6750 and RFC 6819, deprecates the implicit and password grants, and turns a decade of real attacks into requirements. Its own introduction says following it "may break interoperability" with older deployments, and still recommends upgrading "as soon as feasible".
This page lists the mistakes RFC 9700 addresses, each with the section and the exact requirement strength. MUST and MUST NOT are absolute; SHOULD and SHOULD NOT mean you need a specific, documented reason to deviate; MAY is optional. The section numbers link to the RFC.
Summary table
| # | Mistake | Section | Requirement |
|---|---|---|---|
| 1 | Pattern or prefix matching of redirect URIs | 2.1, 4.1.3 | AS MUST use exact string matching |
| 2 | Open redirectors on client or AS | 4.11 | MUST NOT expose |
| 3 | No CSRF protection on the redirect endpoint | 4.7 | Clients MUST prevent CSRF |
| 4 | No mix-up defense with several authorization servers | 4.4 | Defense REQUIRED; iss SHOULD |
| 5 | Codes and state leaking through Referer | 4.2 | No third-party content SHOULD NOT |
| 6 | Tokens in URLs and browser history | 4.3 | Not in query MUST NOT |
| 7 | Loose postMessage origins | 4.17 | Exact origins MUST; no * |
| 8 | No PKCE (code injection, code theft) | 2.1.1, 4.5 | Public MUST; confidential RECOMMENDED |
| 9 | PKCE downgrade accepted | 4.8 | AS MUST mitigate |
| 10 | Using the implicit grant | 2.1.2 | SHOULD NOT |
| 11 | Using the password grant | 2.4 | MUST NOT |
| 12 | Unconstrained (pure bearer) access tokens | 2.2.1 | Sender-constrain SHOULD |
| 13 | Unprotected refresh tokens for public clients | 2.2.2, 4.14 | Rotate or sender-constrain MUST |
| 14 | Over-privileged, all-audience access tokens | 2.3 | Restrict SHOULD; RS refuse MUST |
| 15 | Resource servers mishandling tokens | 4.9 | Treat as secrets MUST |
| 16 | Weak or no client authentication | 2.5 | Enforce SHOULD; asymmetric RECOMMENDED |
| 17 | Client IDs that collide with user IDs | 4.15 | SHOULD NOT allow; else MUST disambiguate |
| 18 | Authorization pages that can be framed | 4.16 | AS MUST prevent clickjacking |
| 19 | 307 redirect after a credential POST | 4.12 | MUST NOT use 307; 303 SHOULD |
| 20 | Plain HTTP; trusting proxy headers | 2.6, 4.13 | No unencrypted responses MUST NOT; sanitize MUST |
| 21 | CORS on the authorization endpoint; no metadata | 2.6 | CORS MUST NOT; metadata RECOMMENDED |
RFC 9700 also states an attacker model (§3): web attackers, network attackers, attackers who can read the authorization response (A3) or the request (A4), and attackers who obtain an access token (A5). It says implementers MUST take into account all possible types of attackers in their environment. The mistakes below are what those attackers exploit.
Redirects and the browser
1. Loose redirect URI matching
When authorization servers allowed redirect URI patterns (wildcards in the host, prefix matches on the path), attackers found URIs that matched the pattern but pointed somewhere they could read, and harvested codes and tokens. §2.1: authorization servers MUST use exact string matching against pre-registered URIs. The single exception is the port number of loopback redirects for native apps, where the server MUST allow a variable port (§4.1.3, following RFC 8252 §7.3). If the origin and integrity of the request are verified, for example with PAR or signed request objects plus client authentication, the server MAY trust the redirect URI without further checks.
2. Open redirectors
A URL like https://client.example/go?to=https://attacker.example on the client's domain lets an attacker build a redirect URI that passes registration checks and then forwards the code (or, with fragments, the token) to them. §2.1 and §4.11: clients and authorization servers MUST NOT expose open redirectors. Authorization servers can become open redirectors too: a malicious client can send a deliberately invalid or declined request and have the user forwarded to its phishing page. The AS MUST take precautions, MUST authenticate the user before redirecting (except for silent authentication), and SHOULD only redirect automatically to URIs it trusts (§4.11.2).
3. No CSRF protection
An attacker sends the victim's browser to the client's redirect URI with the attacker's own code, so the victim's session ends up linked to the attacker's account (for example, saving data into it). §2.1: clients MUST prevent CSRF. Clients that have made sure the AS supports PKCE MAY rely on it; OpenID Connect's nonce also provides it; otherwise one-time CSRF tokens in state, bound to the user agent, MUST be used. Before relying on PKCE for this, clients MUST ensure the server supports it (§4.7.1). If state carries application data whose integrity matters, it MUST be protected against tampering and swapping.
4. No mix-up defense
A client that works with several authorization servers (say, "sign in with" several providers) can be tricked: the user picks the attacker's server, the attacker redirects to the honest server, and the client sends the honest server's code to the attacker's token endpoint. §2.1: when a client can interact with more than one AS, a mix-up defense is REQUIRED. Clients SHOULD use the iss authorization response parameter of RFC 9207 (or the iss in an ID token or JARM response, processed the same way); otherwise they MAY use a distinct redirect URI per issuer, which §4.4.2.2 says SHOULD only be used if other options are not available. Either way, the client MUST store which issuer each request was sent to, bound to the user agent, and MUST abort on a mismatch (§4.4.2).
HTTP/1.1 302 Found
Location: https://client.example/cb?code=x1848ZT64p4IirMPT0R-X3141MFPTuBX-VFL_cvaplMH58
&state=ZWVlNDBlYzA1NjdkMDNhYjg3ZjUxZjAyNGQzMTM2NzI
&iss=https%3A%2F%2Fhonest.as.example
Example from RFC 9207 §2.1. The client compares the decoded iss with the issuer it sent this request to, using simple string comparison.
5. Leaking codes and state through Referer
If the page at the redirect URI includes third-party images, scripts, ads, or links, the browser may send the full URL, with the code and state, in the Referer header. §4.2.4: the page rendered from the authorization response, and the authorization endpoint, SHOULD NOT include third-party resources or links to external sites. Further measures: a Referrer-Policy: no-referrer header, the code response type, PKCE or a confidential client so a leaked code is useless, single-use codes (RFC 6749 already says codes MUST be invalidated after first use), invalidating state after first use (SHOULD), and the form post response mode.
6. Tokens in URLs and browser history
URLs are stored in history, server logs and proxies. §4.3.2: clients MUST NOT pass access tokens in a URI query parameter in the way RFC 6750 §2.3 describes. Send them in the Authorization header. For codes in history, rely on replay prevention (single use, PKCE) or form post response mode (§4.3.1).
7. Loose postMessage origins
Some browser-based flows return the authorization response by postMessage instead of a redirect. §4.17.2: the AS MUST compare receiver origins by exact string matching and send messages only to the trusted client origin; wildcard origins like "*" MUST NOT be used; clients MUST check the sender's origin exactly; and all the protections of §2.1 MUST apply equally.
Codes, PKCE and grant types
8. Not using PKCE
In authorization code injection, an attacker who has stolen a code (through any of the leaks above) injects it into their own session with the legitimate client, which redeems it and logs the attacker in as the victim. Client authentication does not help, because the legitimate client does the redemption. §2.1.1: clients MUST prevent code injection. Public clients MUST use PKCE; for confidential clients PKCE is RECOMMENDED; confidential OpenID Connect clients MAY use the nonce instead, with the precautions of §4.5.3.2. The challenge or nonce MUST be transaction-specific and bound to the client and user agent. Clients SHOULD use a challenge method that does not expose the verifier: currently only S256. Authorization servers MUST support PKCE, MUST enforce the verifier when a challenge was sent, and MUST provide a way to detect PKCE support (code_challenge_methods_supported in metadata is RECOMMENDED). Details: authorization code with PKCE.
9. Accepting a PKCE downgrade
If PKCE is optional, an attacker removes code_challenge from a request on their own device, gets an unbound code, and injects it into the victim's session; the client sends its verifier, and a lax server ignores it. §4.8.2: authorization servers MUST mitigate this by rejecting a token request that contains code_verifier when the authorization request had no code_challenge. Servers that require PKCE for all clients get this for free.
10. Using the implicit grant
The implicit grant returns access tokens in the URL fragment, where they can leak (§4.1, 4.2, 4.3), be injected (§4.6), and cannot be sender-constrained, since there is no standard way to bind them at the authorization endpoint. §2.1.2: clients SHOULD NOT use the implicit grant or any response type that issues access tokens in the authorization response, unless token injection is prevented and the leakage vectors are mitigated; they SHOULD use the code response type (or another type that returns access tokens from the token endpoint, such as code id_token).
11. Using the resource owner password credentials grant
§2.4: it MUST NOT be used. It exposes the user's password to the client, widens where it can leak, trains users to type it into non-AS pages, and cannot support multi-step, two-factor or origin-bound (WebAuthn) authentication.
Tokens
12. Pure bearer access tokens
A bearer token works for anyone who holds it. §2.2.1: authorization and resource servers SHOULD use mechanisms for sender-constraining access tokens, such as mutual TLS (RFC 8705) or DPoP (RFC 9449). §4.10 notes the limit: if an attacker gets both the token and the key (for example via XSS in the client), sender-constraining is undermined, though keys held in a hardware or non-extractable store still stop use once the client is offline. See sender-constrained tokens.
13. Unprotected refresh tokens
§2.2.2: refresh tokens for public clients MUST be sender-constrained or use refresh token rotation. §4.14.2 adds that the AS MUST decide by risk assessment whether to issue refresh tokens at all, that refresh tokens MUST be bound to the consented scope and resource servers, that they SHOULD expire after client inactivity, and that the AS MAY revoke them on events such as password change or logout. If the grant is encoded inside the refresh token, its integrity MUST be protected.
14. Over-privileged access tokens
§2.3: privileges SHOULD be restricted to the minimum required; tokens SHOULD be audience-restricted to one resource server or a small set; and a resource server receiving a token not meant for it MUST refuse the request. Tokens SHOULD also be restricted to specific resources and actions. The aud claim (RFC 9068), the resource parameter (RFC 8707), scope, and authorization_details (RFC 9396) are the tools. See scopes and consent.
15. Token handling at the resource server
A counterfeit resource server can phish tokens from clients that learn API URLs at runtime, and a compromised one can replay what it receives (§4.9). §4.9.3: sender-constrained tokens and audience restriction SHOULD be used to stop replay elsewhere, and the resource server MUST treat access tokens like other sensitive secrets, not storing or transferring them in plaintext. Log token hashes or IDs, never tokens.
Clients and authorization servers
16. Weak client authentication
§2.5: authorization servers SHOULD enforce client authentication where it is feasible to issue and protect client credentials, and asymmetric methods (mutual TLS, or private_key_jwt per RFC 7523) are RECOMMENDED, so the server holds no symmetric secrets that could leak. See client authentication methods.
17. Client identifiers that look like users
If a resource server cannot distinguish a token issued to a client on its own behalf from one issued for a user, and clients can choose their client_id, a client can register an ID equal to some user's sub and access that user's resources. §4.15.1: authorization servers SHOULD NOT let clients influence their client_id or any claim that could be confused with a genuine resource owner, where client IDs and user identifiers share a namespace; where that cannot be avoided, they MUST give resource servers another way to tell the token types apart.
18. Clickjacking the authorization page
An attacker frames the consent page invisibly and tricks the user into clicking "Allow", or overlays a fake password field. §4.16: authorization servers MUST prevent clickjacking and SHOULD use CSP level 2 or later (frame-ancestors) on the authorization endpoint and every page used to authenticate the user or authorize the client, including device authorization, login and error pages. Because older browsers lack CSP, it SHOULD be combined with other techniques such as X-Frame-Options unless such browsers are explicitly unsupported.
19. Redirecting with 307 after a credential POST
A 307 redirect tells the browser to repeat the POST, form body and all, at the new location. If the AS answers a login form POST with a 307 to the client, the client receives the user's password. §4.12: authorization servers that redirect a request that may contain user credentials MUST NOT use 307, and if they use an HTTP redirect they SHOULD use 303 (See Other), the only status code that unambiguously forces a switch to GET.
Transport and infrastructure
20. Plain HTTP and trusting proxy headers
§2.6: authorization responses MUST NOT be sent over unencrypted connections, so authorization servers MUST NOT allow http redirect URIs except loopback redirects for native clients (RFC 8252 §7.3). End-to-end TLS between client and resource server is RECOMMENDED. Where TLS terminates at a reverse proxy (§4.13), the proxy MUST sanitize inbound requests so security-relevant headers (client IP, client certificate) cannot be injected from outside, and the link from proxy to application MUST be protected against eavesdropping, injection and replay. A proxy that passes through an attacker-supplied client-certificate header defeats certificate-bound tokens entirely.
21. CORS in the wrong place, and no metadata
§2.6: endpoints browser clients call directly (token, metadata, JWKS, registration) MAY support CORS, but CORS MUST NOT be supported at the authorization endpoint, which the client never calls directly. Publishing RFC 8414 authorization server metadata, and having clients use it, is RECOMMENDED: it enables security features automatically, avoids misconfigured endpoint URLs, and eases key rotation.
What to do
If you only do six things:
- Authorization code with PKCE (
S256) for every user-facing client; turn off implicit and password grants. - Exact redirect URI matching; no open redirectors anywhere on client or AS domains.
- Reject
code_verifierwithout a prior challenge; usestateanyway. - Audience-restricted, least-privilege access tokens, checked by every API.
- Rotate or sender-constrain public clients' refresh tokens; sender-constrain access tokens where you can.
- Check
iss(RFC 9207) if a client talks to more than one authorization server.
Then work through the table above. The OAuth flow explorer shows where in each flow these checks sit.
Sources
- RFC 9700: Best Current Practice for OAuth 2.0 Security, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 6749: The OAuth 2.0 Authorization Framework, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 7636: Proof Key for Code Exchange by OAuth Public Clients, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 9207: OAuth 2.0 Authorization Server Issuer Identification, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 8705: OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP), IETF. IETF Trust Legal Provisions (brief quotations with attribution).
- RFC 8414: OAuth 2.0 Authorization Server Metadata, IETF. IETF Trust Legal Provisions (brief quotations with attribution).