Initializing Enclave...

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_id in 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 with invalid_client.
  • How to fix it: Cross-reference the client_id value 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:

  1. 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.

  2. Wrong environment bleed: A client_id from dev user pool is deployed to prod. Auth fails silently in production while dev works fine — the worst kind of outage to diagnose.

  3. 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.

  4. 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_client error. 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.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →