Protocol mandates under RFC 9700
As of January 2025, RFC 9700 establishes the Best Current Practice for OAuth 2.0 Security, significantly updating the threat models and requirements originally defined in RFC 6749 and RFC 6819. The protocol has evolved to address dynamic client relationships, multi-tenant environments, and the increasing sophistication of authorization code injection and token leakage attacks.
A primary mandate of RFC 9700 is the deprecation of the implicit grant type and the resource owner password credentials grant. Application engineers must transition to the authorization code grant for all client types. To secure this grant, Proof Key for Code Exchange (PKCE) is now required for public clients and strongly recommended for confidential clients. Furthermore, authorization servers are required to enforce exact string matching for redirect URIs, eliminating pattern matching vulnerabilities that attackers historically exploited to exfiltrate authorization codes.
PKCE mechanics and concrete worked example
Proof Key for Code Exchange binds an authorization code to the specific client transaction that requested it, preventing authorization code injection where an attacker intercepts a legitimate code and injects it into their own session. The client generates a cryptographically random code_verifier and computes a code_challenge using the S256 method (SHA-256 hash, base64url-encoded).
Consider a hypothetical synthetic fixture for an authorization request. The client generates a high-entropy string for the code_verifier, for instance, a 43-character random string. The client then hashes this string using SHA-256 and base64url-encodes the result to produce the code_challenge. The client initiates the flow by redirecting the user agent to the authorization endpoint, appending the query parameters response_type=code, client_id=fixture_client, code_challenge=calculated_hash, and code_challenge_method=S256.
Upon successful authentication, the authorization server returns an authorization code. When the client exchanges this code at the token endpoint, it includes the original plaintext code_verifier in the POST request body. The authorization server computes the SHA-256 hash of the received code_verifier and compares it to the code_challenge recorded during the initial request. If the values match, the token is issued. If an attacker injects a stolen code but lacks the original code_verifier, the token request fails.
- Generate a cryptographically random code_verifier between 43 and 128 characters long.
- Calculate the code_challenge using the S256 method; do not use the plain method as it exposes the verifier in the authorization request.
- Transmit the code_challenge and code_challenge_method in the initial authorization redirect.
- Submit the original code_verifier in the token exchange request.
Strict redirect URI validation and mix-up attacks
RFC 9700 mandates that authorization servers utilize exact string matching when comparing client redirection URIs against pre-registered URIs. The only permitted exception is the port number in localhost redirection URIs for native applications. Regular expression matching or partial path matching introduces severe risks of open redirect vulnerabilities, which attackers leverage to steal authorization codes.
In deployments where a client interacts with multiple authorization servers, mix-up attacks become a critical threat. An attacker could confuse the client into sending an authorization code issued by an attacker-controlled server to an honest server, or vice versa. To mitigate this, RFC 9700 requires clients to implement defenses defined in RFC 9207, specifically using the iss parameter in the authorization response. The authorization server includes its issuer identifier in the response, allowing the client to verify that the code originated from the expected authorization server before proceeding to the token endpoint.
State parameter validation and CSRF defense
Historically, the state parameter was the primary defense against Cross-Site Request Forgery (CSRF) in OAuth 2.0 flows. A client would generate an unguessable state value, bind it to the user agent session, and verify that the exact value was returned in the authorization response. RFC 9700 clarifies that clients utilizing PKCE may rely on the CSRF protection natively provided by the PKCE mechanism, provided the authorization server enforces PKCE strictly.
However, maintaining the state parameter remains best practice for application state restoration. If the state parameter is used for CSRF protection in the absence of PKCE (which is not recommended under current guidelines), it must be a one-time use token securely bound to the user agent. OpenID Connect flows alternatively utilize the nonce parameter to provide CSRF protection and prevent replay attacks, ensuring the ID token is cryptographically bound to the initial request.
Safe test matrix with expected outcomes
To verify adherence to RFC 9700, engineering teams should evaluate their OAuth 2.0 implementations against a matrix of expected allowed and denied outcomes using synthetic test fixtures. This matrix ensures that downgrade attacks and misconfigurations are systematically rejected by the authorization server.
- Scenario: Authorization request missing code_challenge. Expected Outcome: Denied for public clients; optionally allowed but discouraged for confidential clients.
- Scenario: Token request with code_verifier, but authorization request lacked code_challenge. Expected Outcome: Denied (mitigates PKCE downgrade attacks).
- Scenario: Redirect URI matches exactly except for an appended query parameter. Expected Outcome: Denied (exact string matching required).
- Scenario: Native client uses localhost redirect URI with a different port than registered. Expected Outcome: Allowed (RFC 8252 exception).
- Scenario: Authorization response sent over unencrypted HTTP scheme. Expected Outcome: Denied (except for loopback native clients).
Implementation tradeoffs and failure modes
Enforcing exact string matching for redirect URIs increases operational friction. Development teams frequently rely on dynamic subdomains or varying path segments for feature branch deployments. Shifting to exact matching requires automated client registration processes or centralized proxy endpoints that handle the exact redirect before securely passing the code to the dynamic environment. Failure to manage this tradeoff often results in developers reverting to insecure pattern matching.
A common failure mode in PKCE implementation is generating a static code_verifier or failing to bind it securely to the user agent session. If the verifier is predictable or globally shared, the protection against code injection is entirely neutralized. Similarly, failing to mandate the S256 challenge method allows attackers capable of reading the authorization request URL to extract the plaintext verifier and execute the injection attack.
Evidence limits and sources
Drafted with Gemini and checked for source links, advisory identifiers, structure and unsupported product claims. Examples are synthetic and are not scan results.
Drafted with Gemini and checked for source links, advisory identifiers, structure and unsupported product claims. Examples are synthetic and are not scan results.
Prepared with AI assistance and published by WarHound AI. Check the references and evidence limits in each article.
The WarHoundAI journalQuestions or a factual correction? Get in touch with the article URL and supporting evidence.