How to Fix Cognito Invalid Client ID Error in Authentication Requests
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: The
client_idin your Cognito auth request does not match any App Client registered under the target User Pool, or it belongs to a different pool/region entirely — Cognito rejects the request withinvalid_client. - How to fix it: Cross-reference the
client_idvalue in your code/env against the exact App Client ID in the AWS Console under Cognito → User Pools → [Your Pool] → App clients. Confirm region and pool ID alignment. - Shortcut: Use our Client-Side Sandbox above to auto-refactor this — paste your auth config and get corrected initialization code without sending secrets to any server.
The Incident (What Does the Error Mean?)
Raw error from Cognito token endpoint or SDK:
POST https://cognito-idp.us-east-1.amazonaws.com/
Response: 400 Bad Request
{
"__type": "NotAuthorizedException",
"message": "Invalid client id"
}
Or via Hosted UI / OAuth2 token endpoint:
HTTP 400
{"error": "invalid_client", "error_description": "Client authentication failed"}
Immediate consequence: Every single authentication attempt — sign-in, sign-up, token refresh, federated login — returns a hard failure. No tokens are issued. Your application's auth flow is completely down for all users hitting this code path.
The Attack Vector / Blast Radius
This is not just a config typo — the blast radius depends on how the wrong client ID got there:
Deleted App Client still referenced in code: The App Client was rotated or deleted (common post-security-audit), but the old ID persists in environment variables or a secrets manager. 100% auth failure immediately.
Wrong environment bleed: A
client_idfromdevuser pool is deployed toprod. Auth fails silently in production while dev works fine — the worst kind of outage to diagnose.Multi-tenant pool misconfiguration: In SaaS architectures with per-tenant pools, routing logic sends a request to the wrong pool's endpoint with a mismatched client ID. This can cause cross-tenant auth confusion and audit log gaps.
Security implication: If your App Client has a client secret enabled (server-side flow) but your frontend is sending requests without it — or vice versa — Cognito rejects with the same
invalid_clienterror. A client secret exposed in frontend JS is a credential leak. Cognito is protecting you here, but the underlying misconfiguration signals a broader secrets hygiene problem.
How to Fix It
Step 1: Locate the Correct Client ID
Navigate to:
AWS Console → Cognito → User Pools → [your-pool-id] → App clients
Copy the exact Client ID (26-character alphanumeric string). Confirm the User Pool ID and region match your endpoint URL.
Basic Fix — Environment Variable / SDK Init Correction
// AWS Amplify config (amplify-config.js or aws-exports.js)
const awsConfig = {
Auth: {
region: 'us-east-1',
- userPoolId: 'us-east-1_XXXXXXXXX',
- userPoolWebClientId: '1abc2defghij3klmnopqrstu45', // ❌ stale/wrong client ID
+ userPoolId: 'us-east-1_A1B2C3D4E', // ✅ verified from Console
+ userPoolWebClientId: '6n7o8pqrstuvwxyz9a0b1c2d3e', // ✅ current App Client ID
}
};
# .env or SSM Parameter
- COGNITO_CLIENT_ID=1abc2defghij3klmnopqrstu45
+ COGNITO_CLIENT_ID=6n7o8pqrstuvwxyz9a0b1c2d3e
Direct Token Endpoint Fix (OAuth2 / curl)
curl -X POST https://your-domain.auth.us-east-1.amazoncognito.com/oauth2/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
- -d 'grant_type=authorization_code&client_id=WRONG_CLIENT_ID&code=AUTH_CODE&redirect_uri=https://app.example.com/callback'
+ -d 'grant_type=authorization_code&client_id=6n7o8pqrstuvwxyz9a0b1c2d3e&code=AUTH_CODE&redirect_uri=https://app.example.com/callback'
Enterprise Best Practice — Client Secret Handling
If your App Client has a client secret (required for server-side/machine-to-machine flows), never use it in browser/mobile clients. Create a separate App Client with no secret for public clients.
# Terraform: aws_cognito_user_pool_client
resource "aws_cognito_user_pool_client" "web_client" {
name = "web-app-client"
user_pool_id = aws_cognito_user_pool.main.id
- generate_secret = true # ❌ NEVER for SPAs or mobile apps
+ generate_secret = false # ✅ public client — no secret
+ explicit_auth_flows = [
+ "ALLOW_USER_SRP_AUTH",
+ "ALLOW_REFRESH_TOKEN_AUTH"
+ ]
+ allowed_oauth_flows_user_pool_client = true
+ allowed_oauth_flows = ["code"]
+ allowed_oauth_scopes = ["openid", "email", "profile"]
+ callback_urls = ["https://app.example.com/callback"]
}
For server-side clients (Lambda, backend API), store the client secret in AWS Secrets Manager — not env vars:
- const clientSecret = process.env.COGNITO_CLIENT_SECRET; // ❌ env var, rotates poorly
+ const secret = await secretsManager.getSecretValue({ SecretId: 'prod/cognito/client-secret' }).promise();
+ const clientSecret = JSON.parse(secret.SecretString).client_secret; // ✅ rotatable, auditable
💡 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
1. Validate Client ID Exists at Deploy Time
Add a pre-deploy smoke test in your pipeline that calls the Cognito DescribeUserPoolClient API to assert the client ID is valid before pushing to production:
# GitHub Actions / pre-deploy step
aws cognito-idp describe-user-pool-client \
--user-pool-id $COGNITO_USER_POOL_ID \
--client-id $COGNITO_CLIENT_ID \
--region $AWS_REGION \
|| { echo "❌ COGNITO_CLIENT_ID is invalid. Blocking deploy."; exit 1; }
2. Checkov — Terraform Static Analysis
Checkov flags generate_secret = true on public-facing clients:
checkov -d ./terraform --check CKV_AWS_131
# CKV_AWS_131: Ensure Cognito UserPool client does not expose secret to public app
3. AWS Config Rule
Enable the managed rule cognito-user-pool-mfa-configuration and write a custom Config rule that alerts when a User Pool App Client is deleted while still referenced in active Parameter Store paths.
4. Secrets Manager Rotation + SSM Tagging
Tag all SSM parameters storing Cognito client IDs with the source User Pool ARN:
resource "aws_ssm_parameter" "cognito_client_id" {
name = "/prod/app/cognito_client_id"
type = "SecureString"
value = aws_cognito_user_pool_client.web_client.id
tags = {
UserPoolArn = aws_cognito_user_pool.main.arn # traceability
Environment = "prod"
}
}
This creates an auditable link — if the pool is destroyed, the tag reference breaks and your IaC drift detection catches it before deployment.