
OAuth 2.0 is the protocol that powers "Log in with Google," "Connect with GitHub," and every third-party authentication flow that has become standard infrastructure for modern web applications. It works remarkably well for what it was designed to do: delegate authorization from a resource owner to a client application without exposing credentials.
It also has a class of vulnerability that is structurally underappreciated, consistently misimplemented, and capable of producing complete account takeover in production applications that have been running in production for years without anyone noticing. The vulnerability is in redirect URI validation the mechanism that controls where the authorization server sends the user after they authenticate.
Get it right and the OAuth flow is safe. Get it subtly wrong and you have handed an attacker a mechanism to steal authorization codes and access tokens for any user who can be induced to click a link. The subtlety is the problem. The misconfiguration that produces this vulnerability does not look broken. The flow works correctly for every legitimate user. The authorization server sends the user exactly where it is supposed to. The gap only becomes visible when someone asks what happens when the redirect URI is not exactly what it is supposed to be.
How OAuth 2.0 Authorization Code Flow Works
Before examining where it fails, the mechanism needs to be clear.
In the authorization code flow the variant that matters most for web applications authentication proceeds in a sequence that is designed to keep credentials away from the client application while still allowing the application to act on the user's behalf.
The sequence is: the user initiates login, the client application redirects the user's browser to the authorization server with a request that includes the client ID, the requested scopes, a state parameter, and a redirect URI. The user authenticates with the authorization server. The authorization server issues an authorization code and redirects the user's browser to the redirect URI with the code as a query parameter. The client application exchanges the authorization code for an access token at the token endpoint.
The redirect URI is the mechanism by which the authorization code travels from the authorization server to the client application. It is a URL parameter supplied by the client in the initial authorization request, and it tells the authorization server where to send the user and the authorization code after authentication.
The security of the entire flow depends on the authorization server verifying that the redirect URI in the authorization request matches a URI that was registered for that client when the application was configured. If the redirect URI is not validated or validated insufficiently an attacker can supply their own URI and cause the authorization server to redirect the user, and the authorization code, to a location the attacker controls.

The Three Validation Gaps That Produce Exploitable OAuth
Redirect URI validation failures come in recognizable patterns. Understanding the specific patterns is essential because they look different from each other and require different testing approaches to find.
Pattern One: No Validation
The simplest failure: the authorization server accepts any redirect URI without validation against a registered list. An attacker who discovers this can craft an authorization request with redirect_uri=https://attacker.com/callback and the authorization server will redirect the user and the authorization code to the attacker's domain after authentication.
This pattern is less common in mature OAuth providers but appears regularly in internally-built OAuth servers, in OAuth implementations using older libraries, and in test or staging configurations where the redirect URI validation was disabled for convenience and never re-enabled in production.
The attack: Craft a URL of the form:
https://auth.target.com/oauth/authorize?
client_id=TARGET_APP&
redirect_uri=https://attacker.com/callback&
response_type=code&
scope=read
Send this URL to a victim. When the victim authenticates, the authorization server redirects their browser to https://attacker.com/callback?code=AUTHORIZATION_CODE. The attacker now has an authorization code for the victim's account. Depending on the token endpoint configuration, they can exchange this code for an access token without the client secret (for public clients) or with a client secret they obtained through other means.
Pattern Two: Prefix Matching Instead of Exact Matching
A more common and harder-to-spot failure: the authorization server validates that the redirect URI starts with a registered prefix rather than exactly matching a registered URI.
If the registered redirect URI is https://app.company.com/auth/callback and the validation checks only that the submitted URI starts with https://app.company.com, an attacker can submit https://app.company.com.attacker.com/callback or https://app.company.com/auth/callback/../../../steal and the prefix check will pass.
The first variant https://app.company.com.attacker.com exploits the fact that a domain beginning with the registered domain's string is not the registered domain. The prefix check passes because the URI starts with https://app.company.com. The actual destination is the attacker's domain.

Pattern Three: Wildcard Subdomain Matching
Some authorization server configurations allow wildcard redirect URIs registering https://*.company.com/callback to cover all subdomains. This is sometimes done to accommodate feature branch deployments, development environments, or customer-specific subdomain configurations.
The risk: an attacker who can create or find any subdomain under company.com can use that subdomain as a valid redirect destination. If the application uses user-supplied subdomains customername.company.com and an attacker can register a subdomain, the wildcard redirect URI allows them to receive authorization codes for any user who authenticates. If the application has any open redirect vulnerability on any subdomain, that subdomain can be used to forward the authorization code to the attacker.
Open redirects and wildcard OAuth redirect URIs are a particularly dangerous combination. An open redirect on any subdomain covered by the wildcard means the attacker can construct a URI that passes the wildcard validation but ultimately delivers the authorization code to an external attacker-controlled destination.
The State Parameter: The Protection That Gets Omitted
The OAuth specification includes a state parameter in the authorization request for a specific reason: to prevent cross-site request forgery attacks against the OAuth flow. The state is a value generated by the client application before the authorization request, included in the request, and verified in the callback. If the state in the callback does not match the state that was generated, the client should reject the callback.
Without state validation, an attacker can construct a crafted authorization request that causes the victim's browser to initiate an OAuth flow and then link their own account to the victim's OAuth identity. The attack is specific: the attacker begins an OAuth flow on their own behalf, captures the authorization URL before authenticating, and sends that URL to the victim. When the victim's browser follows the URL and authenticates, the authorization server issues a code for the victim's account and redirects to the callback. The victim's browser submits the code to the application. Without state validation, the application has no way to know that this was not a flow the victim initiated.

The state parameter makes this attack fail because the victim's browser submits a code that was associated with the attacker-generated state, and the victim's session has no matching state value to validate against. The mismatch causes the client to reject the callback.
State validation is specified in the OAuth RFC. It is optional in the sense that the RFC does not use MUST language. It is mandatory in the sense that omitting it creates a meaningful CSRF vulnerability in the OAuth flow. A large fraction of OAuth implementations in the wild do not implement state validation.
The PKCE Requirement: Authorization Code Interception
Proof Key for Code Exchange PKCE, pronounced "pixie" was introduced in RFC 7636 to protect authorization code flows against authorization code interception attacks, particularly in public clients where the client secret cannot be kept confidential.
The mechanism is elegant: before the authorization request, the client generates a random string called the code verifier. It hashes the code verifier using SHA-256 to produce the code challenge. The code challenge is sent in the authorization request. When the client later exchanges the authorization code for an access token, it sends the code verifier. The authorization server hashes the code verifier and confirms it matches the code challenge that was sent in the original authorization request.
The protection: even if an attacker intercepts the authorization code through a redirect URI validation gap, a compromised redirect endpoint, or code leakage through referrer headers they cannot exchange it for an access token without the code verifier, which was generated by the legitimate client and never left it.
PKCE was originally designed for mobile and native applications where client secrets are impractical to protect. The current OAuth security best practices (RFC 9700, published 2025) recommend PKCE for all authorization code flows, including confidential web application clients. The recommendation exists because PKCE provides meaningful protection even in contexts where client secrets are available it adds a second factor of proof that the entity exchanging the code is the same one that initiated the authorization request.

Many web application OAuth implementations still do not use PKCE. The client secret is available, so PKCE is treated as unnecessary. This reasoning is correct for the threat PKCE was originally designed to address and incorrect for the broader class of code interception attacks that PKCE also mitigates.
Token Leakage Through Referrer Headers
A subtler redirect URI-adjacent vulnerability: access tokens or authorization codes included in the redirect URI as URL fragments or query parameters can leak through HTTP referrer headers when the callback page loads external resources.
When a browser navigates to https://app.company.com/callback?code=AUTHORIZATION_CODE and that page loads a JavaScript file from a third-party CDN, the browser may include the Referer header in the request to the CDN containing the full URL including the authorization code. If the CDN or analytics provider logs referrer headers — and most do — the authorization code is now in a third-party log.
This is not a hypothetical concern. Real incidents have involved authorization code and token leakage through analytics platforms, CDN logs, and error tracking services that receive referrer headers from OAuth callback pages.
The mitigation is straightforward: the OAuth callback page should set a Referrer-Policy: no-referrer header to prevent the browser from including the callback URL in outgoing referrer headers, and the application should consume and invalidate the authorization code as quickly as possible after the callback arrives.
How to Find These Vulnerabilities in Your Own Application
Testing redirect URI validation in an OAuth implementation requires systematic exploration of validation edge cases — not just submitting a different domain and checking whether it is accepted, but probing the specific patterns that partial validation implementations allow through.
Baseline test. Start by identifying the registered redirect URI from the application's OAuth configuration or by observing the authorization request in traffic. The registered URI — for example, https://app.company.com/auth/callback — is your baseline.
Exact match test. Submit the exact registered URI. Confirm it is accepted. This establishes that the flow works correctly with valid input.
Domain confusion variants. Submit URIs that could fool prefix-based or substring-based validation:
https://app.company.com.attacker.com/auth/callbackhttps://app.company.comattacker.com/auth/callbackhttps://attacker.com/app.company.com/auth/callback
If any of these are accepted by the authorization server, the validation is performing substring or prefix matching rather than exact matching.
Path traversal variants. Submit URIs with path traversal sequences after the registered path:
https://app.company.com/auth/callback/../../../stealhttps://app.company.com/auth/callback?redirect=https://attacker.com
If the authorization server normalizes the path before validation, the traversal may not help. If it validates the raw URI before normalization, traversal sequences may allow valid-seeming URIs to resolve to unexpected destinations.
Open redirect chaining. If you can find any open redirect on the registered domain or any subdomain covered by a wildcard registration, test whether an open redirect URL in that domain is accepted as a redirect URI:
https://app.company.com/redirect?url=https://attacker.com
State parameter validation. Initiate an OAuth flow, capture the authorization URL before completing authentication, then submit the code from a different session or without the expected state value. If the application accepts the callback without state validation, a CSRF attack against the OAuth flow is possible.
PKCE presence. Observe whether the authorization request includes a code_challenge and code_challenge_method parameter. Their absence in a public client context or in any client implementing current best practices is a finding.

The Implementation That Gets It Right
Correct redirect URI validation has one primary rule and several secondary practices that collectively close the attack surface.
Primary rule: exact string matching against a registered allowlist. The authorization server must compare the submitted redirect URI character-by-character against the list of URIs registered for the client. Not prefix matching. Not subdomain matching. Not normalized matching. The submitted URI must be identical to one of the registered URIs, including scheme, host, port, and path. Any deviation is a rejection.
This rule produces friction in development environments where developers want flexibility. That friction is appropriate. The redirect URI registration process should be treated as a security-critical configuration, not as a convenience setting.
Secondary practices:
Register redirect URIs for production and staging environments separately, not through wildcard patterns that cover both. Wildcard patterns are not recommended by the OAuth security BCP (RFC 9700).
Implement PKCE for all authorization code flows. The code exchange step should require the code verifier even when a client secret is present.
Validate state parameters on every callback. The state should be cryptographically random, stored server-side or in a secure cookie, and verified before the authorization code is processed.
Set Referrer-Policy: no-referrer on the OAuth callback page.
Consume authorization codes immediately and enforce single-use. An authorization code should be invalidated the moment it is exchanged for a token, and any subsequent attempt to use the same code should be rejected and logged.
Audit registered redirect URIs periodically. Development and test URIs that were registered and never removed are a liability.
Why This Vulnerability Persists in Production
Redirect URI validation gaps persist for the same structural reason that most authentication vulnerabilities persist: the happy path works correctly, so the vulnerability is invisible during normal development and testing.
An OAuth flow with a redirect URI validation gap works perfectly for every legitimate user, because legitimate users initiate the OAuth flow through the application's normal interface, which always submits the correct registered redirect URI. The gap only matters when someone deliberately submits a different redirect URI which only an attacker would do, and only if they know to look for it.
This means the vulnerability class is rarely discovered through normal functional testing, rarely surfaced by traditional scanners that do not understand OAuth flow semantics, and rarely caught in code review unless the reviewer is specifically looking for the pattern.
It is exactly the class of vulnerability that requires explicit, targeted security testing of the OAuth implementation not as a checkbox, but as a systematic exploration of the edge cases that the authorization server's validation logic may handle incorrectly. Those edge cases do not look like attacks in normal traffic. They look like carefully crafted authorization requests that differ from the expected pattern in subtle ways.
Closing: The Flow That Looked Fine
OAuth implementations that ship with redirect URI validation gaps do not look broken. The login works. The callback arrives at the right place. The user is authenticated. Everything that the developer tested works exactly as expected.
The gap is in what was not tested: what happens when the redirect URI is not what it is supposed to be. That question does not get asked in functional testing, does not get answered by static analysis, and does not get surfaced by a scanner that does not understand OAuth flow semantics.
It gets asked by an attacker who reads the RFC, understands the validation patterns that are most commonly misimplemented, and probes each variant methodically against your authorization endpoint. The question takes them fifteen minutes to answer. Your exposure depends on whether your implementation handled the validation correctly not whether it handled the happy path correctly.
The happy path always works. That is never the question that matters.
Axeploit's AI agents test OAuth implementations as part of their authentication flow coverage probing redirect URI validation with domain confusion, path traversal, and open redirect chain variants, verifying state parameter enforcement, checking for PKCE presence, and testing authorization code single-use enforcement. These tests run autonomously against the deployed application, without prior knowledge of the OAuth configuration, because finding the gaps requires exercising the flow with the intent to find them rather than the intent to use them legitimately.





