October 19, 2026
When PKCE Isn’t Enough: How an OAuth Redirect URI Flaw Can Lead to Account Takeover
PKCE protects authorization codes from interception. But what happens when an identity provider sends the code to the wrong destination?

By Sanaullah Aman Korai
6 min read
PKCE is one of the most important security improvements to OAuth 2.0. It helps prevent attackers from stealing an authorization code and exchanging it for tokens, particularly in applications that cannot safely store a client secret.
But enabling PKCE does not automatically make an OAuth implementation secure.
During an authorized assessment of a banking identity platform built on Keycloak, I examined an OAuth/OIDC flow in which PKCE was enabled, but redirect URI validation was not sufficiently restrictive. Under the conditions observed, this weakness could allow an authorization code to be delivered to an untrusted destination after a user authenticated.
The interesting part was that PKCE itself was not the problem. The failure was in another part of the authorization flow.
Let's look at why this matters and what developers should check.
1. A quick refresher on OAuth and PKCE
OAuth 2.0 allows an application to obtain access to protected resources without collecting the user's password directly. OpenID Connect (OIDC) extends OAuth with an identity layer, allowing applications to authenticate users and receive information about their identity.
In a typical authorization code flow, the user is redirected to an identity provider, signs in, and is then redirected back to the client application with an authorization code. The client exchanges that code for tokens, which can be used to access authorized resources.
PKCE, defined in RFC 7636, adds a verification step to the code exchange.
The client generates a random value called a code_verifier. It derives a code_challenge from that value and includes the challenge in the initial authorization request.
Later, when exchanging the authorization code for tokens, the client provides the original verifier. The authorization server checks that it matches the challenge associated with the code.
If someone intercepts the authorization code but does not have the matching verifier, they should not be able to redeem it.
That is the purpose of PKCE.
However, PKCE does not decide whether the requested redirect destination is trustworthy. The authorization server must enforce that separately by validating the redirect_uri.
These controls solve different problems, and both need to work correctly.
2. Where the problem starts
Consider what happens when an attacker can influence the redirect URI in an authorization request.
An attacker can generate their own PKCE verifier and challenge before creating the request. If the identity provider accepts an unauthorized redirect destination, it may send the authorization code to that destination after the user successfully authenticates.
The attacker already knows the verifier because they generated it themselves.
This is an important distinction: the attacker does not need to steal the victim's verifier. They initiated the authorization request and supplied the challenge associated with their own verifier.
If the authorization server accepts the redirect URI and the remaining checks permit the exchange, the attacker may be able to redeem the code.
The victim's login may be entirely legitimate. Their password and one-time password can be correct, and the identity provider can authenticate them successfully.
The failure occurs when the authorization server sends the authorization response to a destination that should not have been allowed.
This is why an OAuth implementation can use PKCE correctly and still have a serious authorization-flow vulnerability.
3. Why redirect URI validation matters
OAuth clients register redirect URIs with the identity provider. During authorization, the requested redirect URI must be validated against the client's registered configuration and the applicable protocol requirements.
Problems can arise when validation rules are too broad, such as unsafe wildcard configurations or matching logic that accepts destinations outside the intended trust boundary.
For example, a team might believe that allowing a broad range of subdomains is convenient. But if an attacker can control one of those subdomains, or otherwise make an unauthorized destination satisfy the validation rule, the configuration may expose the authorization response.
The presence of a wildcard does not automatically make an implementation vulnerable. The actual risk depends on the matching rules, which destinations are trusted, and whether an attacker can control or exploit an accepted destination.
The key question is simple:
Can an authorization request direct the response to a destination that the client and identity provider should not trust?
If the answer is yes, the flow needs further investigation.
4. A simple example of the validation principle
The following Python example illustrates the basic idea of an explicit redirect URI allowlist. It is a teaching example, not a replacement for the identity provider's built-in validation.
REGISTERED_REDIRECT_URIS = {
"https://app.example.test/oauth/callback"
}
def is_allowed_redirect_uri(redirect_uri: str) -> bool:
return redirect_uri in REGISTERED_REDIRECT_URISREGISTERED_REDIRECT_URIS = {
"https://app.example.test/oauth/callback"
}
def is_allowed_redirect_uri(redirect_uri: str) -> bool:
return redirect_uri in REGISTERED_REDIRECT_URISIn a real implementation, redirect URI validation should follow the applicable OAuth/OIDC requirements and the identity provider's supported configuration. Teams should not replace built-in validation with an improvised check without reviewing the protocol requirements.
The broader principle is that authorization responses should only be sent to destinations that have been explicitly approved for the requesting client.
5. What about permissive CORS?
Redirect URI validation is not the only control worth reviewing.
Cross-Origin Resource Sharing (CORS) determines whether browser-based JavaScript from one origin can read responses from another origin. If an identity provider's token endpoint has an overly permissive CORS policy, that can introduce additional risk for browser-based clients.
For example, a policy that reflects arbitrary origins may allow websites that should not be trusted to read certain responses, depending on the endpoint's configuration and the browser's security rules.
CORS should be assessed separately from redirect URI validation. A permissive CORS policy does not, by itself, prove account takeover, and it is not always necessary for an attacker to redeem a code outside the browser.
The impact depends on the complete flow, including the endpoint's requirements, browser behavior, token handling, and client configuration.
For that reason, it is better to document CORS as a separate finding or contributing weakness when the evidence supports it, rather than treating it as the root cause of every OAuth redirect vulnerability.
6. Short-lived authorization codes are not a complete fix
Authorization codes are designed to expire quickly and be used only once. Both controls reduce the opportunity for an attacker to reuse an intercepted code.
But they do not fix an unsafe redirect URI configuration.
If a code reaches an untrusted destination and an attacker has the matching PKCE verifier, an automated exchange may be possible within the code's validity period, subject to the server's other checks.
Short expiration times and single-use enforcement are useful layers of defense. They should not be treated as substitutes for validating the redirect URI.
7. What the secure flow should look like
The authorization server should reject an authorization request when its redirect URI is not permitted for the requesting client.
That decision must happen before the server issues an authorization response. An unapproved destination should not receive an authorization code.
PKCE should remain enabled. The goal is to combine it with proper redirect URI validation, appropriate token endpoint protections, and secure client configuration.
A useful review should cover the entire authorization flow, not just whether a particular security feature is switched on.
8. What I would check during an OAuth assessment
When assessing an OAuth/OIDC deployment, I would review the following areas:
- Registered redirect URIs: Remove unused entries and avoid unnecessarily broad matching rules.
- Redirect validation: Confirm that unauthorized destinations are rejected and that validation follows the relevant protocol requirements.
- PKCE enforcement: Confirm that the authorization server requires and verifies the correct
code_verifier, usingS256where appropriate. - Authorization code handling: Check that codes are short-lived, bound to the appropriate client and redirect URI, and accepted only once.
- CORS policy: Verify that only the necessary origins are allowed and that arbitrary origins are not reflected without justification.
- Token scope and audience: Ensure that tokens grant only the intended permissions and are accepted only by their intended resource servers.
- Impact verification: In an authorized test environment, determine whether the weakness exposes an authorization code, allows token issuance, or provides access to protected resources. These are distinct stages and should be reported accurately.
This last point is especially important. Observing a suspicious redirect is not the same as demonstrating token theft, and obtaining a token is not automatically proof of access to every part of an account.
Good security reporting separates what was observed from what was demonstrated.
The takeaway
The lesson from this assessment is not that PKCE is ineffective. It is that PKCE was never designed to replace redirect URI validation.
PKCE helps ensure that the party redeeming an authorization code possesses the corresponding verifier. Redirect URI validation helps ensure that the authorization response goes to an approved destination.
Both controls are necessary for a secure authorization flow.
If you maintain an OAuth/OIDC deployment, do not stop your review after confirming that PKCE is enabled. Check the redirect URI rules, inspect the token endpoint's configuration, and verify what happens when an authorization request contains an unapproved destination.
Security comes from the controls working together, not from the presence of any single feature.