RAPID INCIDENT TRIAGE & ACTION PLAN•Security
01. Error Signature
Cognito AssumeRole Audience Mismatch Tracker
02. Root Cause Vector

AssumeRoleWithWebIdentity fails with InvalidIdentityToken when the Cognito User Pool client ID in the JWT audience claim doesn't match th...

03. Rapid Prescription

Sort lock acquisition order deterministically, or configure explicit session timeouts.

Zero-Trust Client IDE/ Security

Cognito AssumeRole Audience Mismatch Tracker

AssumeRoleWithWebIdentity fails with InvalidIdentityToken when the Cognito User Pool client ID in the JWT audience claim doesn't match the aud condition in the IAM role's trust policy.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(100L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Legacy PCI zone Cognito integration - DO NOT MODIFY WITHOUT ARCHITECT REVIEW",
  "Resources": {
    "CognitoUserPool": {
      "Type": "AWS::Cognito::UserPool",
      "Properties": {
        "UserPoolName": "legacy-pci-userpool",
        "AutoVerifiedAttributes": ["email"]
      }
    },
    "CognitoIdentityPool": {
      "Type": "AWS::Cognito::IdentityPool",
      "Properties": {
        "IdentityPoolName": "pci-secure-zone-identity",
        "AllowUnauthenticatedIdentities": false,
        "CognitoIdentityProviders": [{
          "ClientId": {
            "Ref": "CognitoUserPoolClient"
          },
          "ProviderName": {
            "Fn::Sub": "cognito-idp.${AWS::Region}.amazonaws.com/${CognitoUserPool}"
          }
        }]
      }
    },
    "CognitoUserPoolClient": {
      "Type": "AWS::Cognito::UserPoolClient",
      "Properties": {
        "ClientName": "legacy-web-app-client",
        "UserPoolId": {
          "Ref": "CognitoUserPool"
        },
        "ExplicitAuthFlows": ["ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_SRP_AUTH"],
        "CallbackURLs": ["https://my-registry.internal/legacy-api/callback"],
        "SupportedIdentityProviders": ["COGNITO"]
        // TODO: Sync audience with IAM role trust policy after migration
      }
    },
    "WebIdentityRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": "PCIAppAssumeRole",
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            "Principal": {
              "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
              "StringEquals": {
                "cognito-identity.amazonaws.com:aud": "us-east-1:abc123def-4567-890a-bcde-f123456789ab"
                // VULNERABILITY: Hardcoded audience from old dev environment, mismatches the actual identity pool ID
              }
            }
          }]
        },
        "Policies": [{
          "PolicyName": "LegacySecretsAccess",
          "PolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [{
              "Effect": "Allow",
              "Action": ["secretsmanager:GetSecretValue"],
              "Resource": [
                "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/legacy/db-uri-*",
                "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/stripe/api-token-*"
              ]
            }]
          }
        }]
      }
    }
  },
  "Outputs": {
    "UserPoolId": {
      "Value": {
        "Ref": "CognitoUserPool"
      }
    },
    "IdentityPoolId": {
      "Value": {
        "Ref": "CognitoIdentityPool"
      }
    },
    "RoleArn": {
      "Value": {
        "Fn::GetAtt": ["WebIdentityRole", "Arn"]
      }
    },
    "DummySecretsNote": {
      "Value": "Secrets injected in env vars below (simulated)"
    }
  },
  "Metadata": {
    "DeploymentScript": "#!/bin/bash\n# OLD DEPLOY SCRIPT - KEPT FOR ROLLBACK\nexport AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nexport AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\nexport STRIPE_API_TOKEN=sk_live_4eC39HqLyjWDarjtT1zdp7dc\nexport DB_URI=postgresql://user:pass@my-registry.internal:5432/pci_legacy_db?sslmode=require\n# TODO: Rotate these after PCI audit next quarter\naws cloudformation deploy --template-file legacy-pci-stack.json --stack-name pci-legacy-stack --capabilities CAPABILITY_IAM"
  }
}
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

Fixing AWS InvalidIdentityToken: Cognito AssumeRoleWithWebIdentity Audience Mismatch in IAM Trust Policy

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins

TL;DR

  • What broke: AssumeRoleWithWebIdentity is rejecting your Cognito JWT because the aud claim in the token (your Cognito App Client ID) does not match the token.actions.githubusercontent.com:aud or cognito-identity.amazonaws.com:aud condition value hardcoded in your IAM role's trust policy.
  • How to fix it: Pull the exact App Client ID from Cognito → App clients, and paste it verbatim into the StringEquals condition for cognito-identity.amazonaws.com:aud (or the correct OIDC audience key) in your trust policy.
  • Fast path: Use our Client-Side Sandbox above to auto-refactor this — paste your trust policy JSON and your Cognito pool config and get the corrected diff without sending your ARNs to any external server.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What does the error mean?)

You hit this raw error from STS:

An error occurred (InvalidIdentityToken) when calling the AssumeRoleWithWebIdentity operation:
  Token is not valid: Token audience is invalid

Or in CloudTrail:

{
  "eventName": "AssumeRoleWithWebIdentity",
  "errorCode": "InvalidIdentityToken",
  "errorMessage": "Token is not valid: Token audience is invalid"
}

Immediate consequence: Every federated authentication attempt fails. Your mobile app, SPA, or backend service cannot obtain temporary AWS credentials. If this is a user-facing login flow backed by Cognito Identity Pools, your entire auth pipeline is down.

STS validates the JWT's aud claim against the StringEquals condition in the trust policy before it even evaluates the role's permission policies. There is no partial success here — it's a hard reject at the federation handshake layer.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not just a misconfiguration inconvenience — the audience condition is a security boundary.

Why the aud check exists: Without a strict audience match, any valid JWT issued by your Cognito User Pool — regardless of which App Client it was issued for — could be used to assume the role. An attacker who obtains a JWT from a low-privilege App Client (e.g., a public-facing web client with minimal scopes) could attempt to assume a role intended only for a trusted backend service client.

Blast radius if you "fix" this by removing the aud condition entirely:

  • Any token from any App Client in your User Pool can assume the role.
  • If your User Pool has a public self-registration flow, an attacker registers, gets a JWT, and calls AssumeRoleWithWebIdentity directly against your backend role.
  • Combined with an overly permissive role policy, this is a full privilege escalation path from anonymous user to AWS API access.

The correct fix is not to remove the condition — it's to set the right value.


How to Fix It (The Solution)

Root Cause Diagnosis

Decode your Cognito JWT (use jwt.io locally, never paste prod tokens into public tools). Check the aud claim:

{
  "sub": "us-east-1_XXXXXXXXX|<user-sub>",
  "aud": "3abc4defgh5ijklmno6pqrstu",
  "token_use": "id"
}

Now check your IAM trust policy. The aud value in the condition must be an exact string match to the above.

Basic Fix

Navigate to: AWS Console → Cognito → User Pools → [Your Pool] → App clients and copy the App Client ID. Then update your trust policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
-         "cognito-identity.amazonaws.com:aud": "us-east-1:WRONG-IDENTITY-POOL-ID-OR-TYPO"
+         "cognito-identity.amazonaws.com:aud": "us-east-1:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

⚠️ Critical distinction: If you are using Cognito Identity Pools (Federated Identities), the aud value is the Identity Pool ID (format: us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx), NOT the User Pool App Client ID. If you are using a custom OIDC trust directly against the User Pool issuer, the aud IS the App Client ID. Mixing these up is the #1 cause of this error.

Enterprise Best Practice

For production, lock down both the audience AND the identity pool, and scope by authenticated users only. Use Terraform to prevent drift:

data "aws_iam_policy_document" "cognito_trust" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = ["cognito-identity.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "cognito-identity.amazonaws.com:aud"
-     values   = ["us-east-1:PLACEHOLDER_NEVER_REPLACED"]
+     values   = [var.cognito_identity_pool_id]
    }

    condition {
      test     = "ForAnyValue:StringEquals"
      variable = "cognito-identity.amazonaws.com:amr"
-     # Missing amr condition — allows unauthenticated role assumption
+     values   = ["authenticated"]
    }
  }
}

Store cognito_identity_pool_id in your Terraform variable file or AWS SSM Parameter Store — never hardcode it inline where it can drift from the actual deployed pool.


💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

1. Checkov — catch placeholder/mismatched audience at plan time:

Write a custom Checkov check or use the built-in CKV_AWS_62 (ensure Cognito roles have constrained trust). Add to your pipeline:

# .github/workflows/iac-scan.yml
- name: Checkov IaC Scan
  uses: bridgecrewio/checkov-action@master
  with:
    directory: ./terraform
    check: CKV_AWS_62,CKV_AWS_156
    soft_fail: false

2. OPA/Conftest — enforce that aud condition is never a wildcard or empty:

# policies/cognito_trust.rego
package aws.iam.trust

deny[msg] {
  stmt := input.Statement[_]
  stmt.Action == "sts:AssumeRoleWithWebIdentity"
  not stmt.Condition.StringEquals["cognito-identity.amazonaws.com:aud"]
  msg := "AssumeRoleWithWebIdentity trust policy missing cognito:aud StringEquals condition"
}

deny[msg] {
  stmt := input.Statement[_]
  aud := stmt.Condition.StringEquals["cognito-identity.amazonaws.com:aud"]
  contains(aud, "PLACEHOLDER")
  msg := sprintf("Cognito aud condition contains unreplaced placeholder: %v", [aud])
}

3. Terraform validation block — fail fast before apply:

variable "cognito_identity_pool_id" {
  type        = string
  description = "Cognito Identity Pool ID for IAM trust policy audience"

  validation {
    condition     = can(regex("^[a-z]{2}-[a-z]+-[0-9]:[a-f0-9-]{36}$", var.cognito_identity_pool_id))
    error_message = "cognito_identity_pool_id must match format: us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  }
}

4. CloudTrail Alarm — alert on repeated InvalidIdentityToken in production:

aws cloudwatch put-metric-filter \
  --log-group-name CloudTrail/DefaultLogGroup \
  --filter-name CognitoAudienceMismatch \
  --filter-pattern '{ ($.eventName = "AssumeRoleWithWebIdentity") && ($.errorCode = "InvalidIdentityToken") }' \
  --metric-transformations metricName=InvalidIdentityTokenCount,metricNamespace=Security/STS,metricValue=1

This catches both misconfiguration regressions after deploys and active token-probing attempts by external actors.

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.What is the difference between the Cognito User Pool App Client ID and the Identity Pool ID, and which one goes in the IAM trust policy aud condition?
The root cause is a fundamental architectural mismatch. When using 'cognito-identity.amazonaws.com' as the federated principal, the trust policy's `aud` condition must be the exact Identity Pool ID (e.g., 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'). The App Client ID is only valid as the `aud` when federating directly with the User Pool's OIDC issuer URL. Confusing these two distinct federation patterns is the primary source of the InvalidIdentityToken error. Understanding this distinction is critical for secure identity bridging.
Q2.Can I use a wildcard in the cognito-identity.amazonaws.com:aud condition to avoid this error?
Absolutely not. Using a wildcard in the `aud` condition with StringLike is a catastrophic security vulnerability, creating a blast radius that allows any Identity Pool in your account—or potentially any pool if misconfigured—to assume the role. Manual fixes are error-prone; you must explicitly list exact IDs. To eliminate this risk, drop the broken IAM trust policy manifest into the StackEngine Local Sandbox above. It will automatically analyze the dependency graph and compile a secure, zero-trust fix with the correct, explicit audience binding.
Q3.Why does this error appear after a Terraform apply or environment promotion even though it worked before?
This is a classic infrastructure drift failure. The Cognito Identity Pool was recreated, generating a new, non-stable ID, while the IAM role's trust policy remained hardcoded to the old ID. Manually wiring Terraform outputs is fragile and does not scale. Across large mono-repos or at enterprise scale, this becomes a compliance nightmare, with audit trails breaking and security postures decaying silently after every deployment. To achieve automated pull request generation with Maker-Checker review and enforce immutable identity bindings, upgrade to the StackEngine CI/CD Automator (Team Edition). It prevents this drift by design, ensuring compliance and security at velocity.