RFC 9700, published in January 2025 as BCP 240, marks which OAuth 2.0 hardening items are MUST and which are only SHOULD. A wildcard redirect URI left in an identity provider console is the kind of setting it rules out, and clean client code does not make up for it. The RFC updates RFC 6749, RFC 6750 and RFC 6819, and warns that following it may break interoperability with older ecosystems. It still recommends upgrading as soon as feasible.
The requirements, by strength
| Setting | Level | RFC 9700 position |
|---|---|---|
| Redirect URI comparison | MUST | Authorization servers use exact string matching |
| Open redirectors | MUST NOT | Clients and authorization servers must not expose them |
| CSRF protection | MUST | Clients protect the redirection endpoint: PKCE, nonce or a one-time state |
| Mix-up defence, several authorization servers | REQUIRED | iss parameter or equivalent iss value; distinct redirection URIs as fallback |
| PKCE, public clients | MUST | Required on the authorization code grant |
| PKCE, confidential clients | RECOMMENDED | OpenID Connect clients MAY use nonce instead, with extra precautions |
code_verifier enforcement | MUST | Enforced at the token endpoint whenever a valid code_challenge was sent |
| PKCE challenge method | SHOULD | One that does not expose the verifier; currently only S256 |
Implicit grant (response_type=token) | SHOULD NOT | Use code or another type that issues tokens at the token endpoint |
| Sender-constrained access tokens | SHOULD | Mutual TLS or DPoP |
| Refresh tokens, public clients | MUST | Sender-constrained or rotated |
Redirect URIs: exact match and no open redirectors
When the authorization server compares the redirect_uri in a request with the registered values, it must use exact string matching. The only exception is the port number in localhost redirection URIs of native apps. Wildcards, prefix matches and "any path under this host" registrations all fail this rule.
The registered list matters too, because every entry is an allowed target for authorization codes. Remove stale entries, especially hosts you no longer control, such as a decommissioned staging domain that someone else could take over.
Open redirectors are the other half. An endpoint that forwards the browser to an arbitrary URI from a query parameter can leak authorization codes and access tokens, and the RFC says neither clients nor authorization servers may expose one. The OWASP example http://example.com/example.php?url=http://malicious.example.com shows the problem: the user sees the trusted host and misses the redirect.
The OWASP cheat sheet on unvalidated redirects gives the fix. Best is to map a short ID to a target on the server instead of taking a URL. If you must accept one, parse it with a maintained parser and compare scheme, canonical host and effective port against an explicit allowlist. Reject userinfo, and never match on raw prefixes or suffixes.
PKCE on every client, mandatory on public ones
Public clients must use PKCE. For confidential clients it is recommended, because it also stops code injection and, as a side effect, CSRF. Confidential OpenID Connect clients may use nonce and the matching ID Token claim instead, but only with the precautions in Section 4.5.3.2. The advice applies to web applications too, although PKCE was designed for native apps.
- The challenge value must change per transaction. A constant value defeats the point.
- Use
S256. RFC 9700 calls it currently the only method that does not expose the verifier in the authorization request; an attacker who can read that request can otherwise break PKCE. RFC 7636 adds thatplainonly mitigates an attacker who sees responses alone. - The verifier is a random string of 43 to 128 characters from the unreserved set. RFC 7636 recommends 32 random octets, base64url-encoded, giving 43 characters.
The server must also block downgrade: a token request with a code_verifier is accepted only if the authorization request included a code_challenge. Publishing code_challenge_methods_supported in the server metadata lets clients detect PKCE support.
Implicit grant, CSRF and mix-up
Clients should not use response_type=token or any response type that returns access tokens in the authorization response. RFC 9700 calls these vulnerable to token leakage and replay, and notes that no standardised method exists to sender-constrain a token issued that way. Use code, or a type that issues tokens at the token endpoint, such as code id_token.
CSRF on the redirection endpoint needs PKCE (if the client has confirmed the server supports it), the nonce in OpenID Connect flows, or a one-time state value bound to the user agent. If your client talks to more than one authorization server, use the iss parameter from RFC 9207 or an equivalent iss value, such as the ID Token claim.
Tokens: sender-constrain where you can
Authorization and resource servers should sender-constrain access tokens with mutual TLS (RFC 8705) or DPoP (RFC 9449), so a stolen token is useless without the matching secret. Refresh tokens for public clients must be sender-constrained or rotated, as described in Section 4.14 of the RFC.
Where to start
- List every registered redirect URI for every client. Remove wildcards, partial paths and hosts you no longer control. Then append a path or change a subdomain in a live authorization request; the server should reject it.
- Search for redirect parameters such as
url,nextorreturnUrlon the client and the authorization server. Replace free-form URLs with server-side ID mapping or a parsed-URL allowlist. - Search client code and server settings for
response_type=token. Move those clients to the authorization code flow with PKCE usingS256. - Test PKCE directly. Send an authorization request without
code_challenge, then redeem the code with acode_verifier. Then redeem a PKCE-protected code without the verifier. Both should fail at the token endpoint; a token in the response means the server fails the test. - Test
state: replay a used value, then submit one from another browser session; the client must reject both. If your provider returnsiss, tamper with it and confirm the client rejects the response. Inspect ID Token claims with our JWT tool, and if you want an outside review, a penetration test can cover the flows. - For public clients, choose sender-constraining or rotation. If you rotate, present a used refresh token twice in a test and see what the server does. Find that attempt in the authorization server logs, since event names differ by product, and alert on it.
