How to Fix AWS Cognito 'redirect_uri Does Not Match Client Settings' Error (OAuth2 Mismatch Debugging Guide)
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: Cognito's authorization server rejected the OAuth2
/oauth2/authorizerequest because theredirect_uriparameter sent by your app does not byte-for-byte match any URI registered under the App Client's Allowed Callback URLs. - How to fix it: Add the exact URI (including scheme, port, path, and trailing slash) to the App Client's callback URL list via the Cognito Console, AWS CLI, or Terraform — then redeploy.
- Use our Client-Side Sandbox above to paste your App Client config and failing authorization URL; it will auto-diff the mismatch and generate the corrected Terraform/CLI snippet locally in your browser.
The Incident (What Does the Error Mean?)
Your users hit a hard wall. The browser receives this from Cognito's hosted UI or token endpoint:
Error: redirect_uri_mismatch
redirect_uri does not match client settings
Or in the raw HTTP response from POST /oauth2/token:
{
"error": "invalid_grant",
"error_description": "redirect_uri does not match client settings"
}
This is not a soft warning. Cognito aborts the entire authorization code exchange. No tokens are issued. Every single user attempting to log in via this client receives a failure. If this is your primary auth flow, this is a full authentication outage.
Cognito's OAuth2 implementation performs a strict string equality check — not a prefix match, not a regex match. The URI in your request must be character-for-character identical to one of the registered callback URLs.
Common triggers that are all treated as different URIs by Cognito:
| Sent in Request | Registered in App Client | Match? |
|---|---|---|
https://app.example.com/callback |
https://app.example.com/callback/ |
❌ No |
http://localhost:3000/callback |
https://localhost:3000/callback |
❌ No |
https://app.example.com/callback |
https://APP.EXAMPLE.COM/callback |
❌ No |
https://app.example.com/callback?foo=bar |
https://app.example.com/callback |
❌ No |
https://app.example.com/auth/callback |
https://app.example.com/callback |
❌ No |
The Attack Vector / Blast Radius
This is not just a misconfiguration annoyance — the strict redirect URI validation is the security control. Here is why Cognito enforces this and what happens when it is misconfigured in the opposite direction (too permissive):
Why strict matching exists: The OAuth2 authorization code is delivered to the redirect_uri. If an attacker can manipulate this value to point to a URI they control (open redirect), they intercept the authorization code and exchange it for tokens — full account takeover. RFC 6749 Section 10.6 mandates exact URI matching for this reason.
Blast radius of this specific error (mismatch rejection):
- 100% of login attempts fail for the affected App Client.
- If you use Cognito as the IdP for multiple service providers (SAML federation, API Gateway authorizers, ALB authentication), all downstream services lose authentication simultaneously.
- Refresh token flows also fail if the
redirect_uriis required and mismatched in the token refresh request. - Mobile apps using the Authorization Code + PKCE flow are equally affected — the custom URI scheme (e.g.,
myapp://callback) must also be registered exactly.
The inverse risk — over-permissive callback URLs (what not to do to "fix" it fast):
Engineers under pressure sometimes register wildcard-style URIs or broad domains. Cognito does not support wildcard callback URLs (unlike some other IdPs). Registering https://example.com to cover all paths is wrong — it will only match requests where redirect_uri=https://example.com exactly. Do not register HTTP variants of production URIs. Do not leave http://localhost:* registered in production App Clients.
How to Fix It (The Solution)
Step 1: Identify the Exact Mismatch
Capture the actual redirect_uri your application is sending. Check your browser's network tab on the failing request to /oauth2/authorize:
GET https://<your-domain>.auth.<region>.amazoncognito.com/oauth2/authorize
?client_id=abc123xyz
&response_type=code
&scope=openid+email
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback <-- THIS VALUE
URL-decode it. That decoded string must exist verbatim in your App Client's callback list.
Basic Fix — AWS Console
- Navigate to Amazon Cognito → User Pools → [Your Pool] → App Clients → [Your Client] → Edit.
- Under Allowed Callback URLs, add the exact decoded URI your app is sending.
- Click Save changes. Changes propagate in under 60 seconds — no redeploy needed.
Basic Fix — AWS CLI
aws cognito-idp update-user-pool-client \
--user-pool-id us-east-1_XXXXXXXXX \
--client-id YOUR_CLIENT_ID \
--callback-urls \
"https://app.example.com/auth/callback" \
"http://localhost:3000/callback" \
--region us-east-1
⚠️ Critical: --callback-urls replaces the entire list, not appends. You must include all existing URLs plus the new one. Fetch the current list first:
aws cognito-idp describe-user-pool-client \
--user-pool-id us-east-1_XXXXXXXXX \
--client-id YOUR_CLIENT_ID \
--query 'UserPoolClient.CallbackURLs'
Enterprise Best Practice — Terraform (IaC)
Managing callback URLs outside of Terraform is the root cause of drift. Lock this down:
resource "aws_cognito_user_pool_client" "app_client" {
name = "my-app-client"
user_pool_id = aws_cognito_user_pool.main.id
callback_urls = [
- "https://app.example.com/callback",
+ "https://app.example.com/auth/callback",
+ "http://localhost:3000/callback",
]
logout_urls = [
- "https://app.example.com/logout",
+ "https://app.example.com/auth/logout",
]
allowed_oauth_flows = ["code"]
allowed_oauth_scopes = ["openid", "email", "profile"]
allowed_oauth_flows_user_pool_client = true
supported_identity_providers = ["COGNITO"]
+ # Prevent console drift — all changes must go through Terraform
+ lifecycle {
+ prevent_destroy = true
+ }
}
For environment-specific URIs, use a variable:
variable "callback_urls" {
type = list(string)
description = "Exact OAuth2 callback URIs for this environment"
+ validation {
+ condition = alltrue([for u in var.callback_urls : startswith(u, "https://") || startswith(u, "http://localhost")])
+ error_message = "All callback URLs must use HTTPS (except localhost for dev)."
+ }
}
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
This error is 100% preventable with pre-deployment validation. Implement the following:
1. Checkov Policy — Block HTTP Callback URLs in Production
Add a custom Checkov check to your pipeline:
# checkov/custom_checks/cognito_callback_https.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class CognitoCallbackHTTPS(BaseResourceCheck):
def __init__(self):
name = "Ensure Cognito callback URLs use HTTPS in production"
id = "CKV_CUSTOM_COGNITO_001"
supported_resources = ["aws_cognito_user_pool_client"]
categories = [CheckCategories.ENCRYPTION]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
urls = conf.get("callback_urls", [[]])[0]
for url in urls:
if url.startswith("http://") and "localhost" not in url:
return CheckResult.FAILED
return CheckResult.PASSED
2. Pre-Deploy Validation Script
Add this to your CI pipeline (GitHub Actions, GitLab CI) before terraform apply:
#!/bin/bash
# validate-cognito-callbacks.sh
# Compares Terraform-planned callback URLs against the app's configured redirect_uri
APP_REDIRECT_URI=$(grep COGNITO_REDIRECT_URI .env.production | cut -d= -f2)
TF_CALLBACKS=$(terraform output -json cognito_callback_urls | jq -r '.[]')
if ! echo "$TF_CALLBACKS" | grep -qF "$APP_REDIRECT_URI"; then
echo "❌ FATAL: redirect_uri '$APP_REDIRECT_URI' is not in Terraform-managed callback list."
echo "Registered callbacks:"
echo "$TF_CALLBACKS"
exit 1
fi
echo "✅ redirect_uri match confirmed."
3. OPA Policy for Terraform Plan Gating
# opa/policies/cognito.rego
package cognito
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_cognito_user_pool_client"
callback := resource.change.after.callback_urls[_]
startswith(callback, "http://")
not contains(callback, "localhost")
msg := sprintf("Cognito App Client '%v' has non-localhost HTTP callback URL: %v", [resource.name, callback])
}
Integrate with Atlantis or Spacelift to gate terraform apply on OPA policy pass.
4. Environment Variable Sync Check
The most common cause of this error in practice is environment variable drift — the COGNITO_REDIRECT_URI in your app's runtime config diverges from what is registered. Enforce consistency:
- Store the canonical callback URL list in AWS Systems Manager Parameter Store or Secrets Manager.
- Have both your Terraform module and your application runtime read from the same SSM parameter.
- This makes it structurally impossible for them to diverge.