SUBJECT: AWS IoT Core rejects MQTT-over-WebSocket (SigV4) CONNECT with
AUTHORIZATION_FAILURE despite verified-correct IAM policy, trust policy,
and valid STS credentials from Cognito Identity Pool
SUMMARY
I am unable to connect to AWS IoT Core's Device Gateway via
MQTT-over-WebSocket using SigV4-signed requests, when the credentials
are obtained through a Cognito Identity Pool (Enhanced/Simplified auth
flow). The connection is rejected with AUTHORIZATION_FAILURE, even
though:
- IAM Policy Simulator reports the action as ALLOWED
- The assumed role has AdministratorAccess attached (full wildcard
permissions) as an isolation test
- sts:get-caller-identity succeeds with the exact same temporary
credentials, confirming the credentials are valid and the STS
session is real
- The same SigV4/WebSocket mechanism works when using AWS Console's
own MQTT test client (IoT Core > Test > MQTT test client), which
connects successfully using the browser session's own credentials
- Two independent client implementations fail identically:
- A hand-written Dart implementation (SigV4 canonical
request/signing verified line-by-line against AWS docs)
- AWS's own official AWSIoTPythonSDK (Python), using
useWebsocket=True with the identical Cognito-derived
credentials
This strongly suggests the issue is specific to how AWS IoT Core
Device Gateway authorizes MQTT-over-WebSocket connections when the
SigV4 credentials originate from a Cognito Identity Pool
AssumeRoleWithWebIdentity session, in this account/region.
ACCOUNT / RESOURCE DETAILS
AWS Account ID : [redacted]
Region : eu-central-1 (Frankfurt)
IoT Endpoint : a3t94id2g1tfgi-ats.iot.eu-central-1.amazonaws.com
Cognito User Pool ID: eu-central-1_FwT24LNdk
Cognito App Client : 123b2bsjohsqqsnrbsauk5dtak (public client)
Identity Pool ID : eu-central-1:cb95f426-646a-4e99-9431-8b68b360de57
Identity Pool flow : Enhanced (Simplified) — "Use default authenticated role"
IAM Role (test) : lava-cognito-authenticated-v2
arn:aws:iam::[redacted]:role/lava-cognito-authenticated-v2
Role permissions : AdministratorAccess (attached for isolation testing)
Role trust policy : (see below)
TRUST POLICY (verified correct, matches AWS documentation pattern)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "eu-central-1:cb95f426-646a-4e99-9431-8b68b360de57"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
REPRODUCTION STEPS
-
Authenticate a Cognito User Pool user via USER_PASSWORD_AUTH
(cognito-idp:InitiateAuth) -> obtain a valid ID token.
-
Exchange the ID token for temporary AWS credentials via the
Identity Pool:
cognito-identity:GetId
cognito-identity:GetCredentialsForIdentity
-> returns valid AccessKeyId / SecretKey / SessionToken, assumed
role ARN: arn:aws:sts::[redacted]:assumed-role/lava-cognito-authenticated-v2/CognitoIdentityCredentials
-
Confirm credentials are valid and functional:
sts:get-caller-identity -> SUCCESS, returns correct Account/Arn/UserId.
-
Attempt to connect to AWS IoT Core via MQTT-over-WebSocket, signed
with AWS Signature Version 4 (service "iotdevicegateway", region
"eu-central-1"), using the above temporary credentials.
Result: WebSocket handshake succeeds (subprotocol "mqtt" is
negotiated), but no CONNACK is ever received. The connection is
silently closed shortly after (observed as WebSocket close code
1005 "No Status Received" on the client side).
-
CloudWatch Logs (log group AWSIotLogsV2) confirms the rejection at
the IoT Device Gateway level:
{
"timestamp": "2026-07-11 09:39:58.190",
"logLevel": "ERROR",
"accountId": "[redacted]",
"status": "Failure",
"eventType": "Connect",
"protocol": "MQTT",
"clientId": "rfert-eu-central-1-29345eec-2ac1-c58d-97a3-5520c9e160a7",
"principalId": "AROASSNC65QVOYX3OXE6E:CognitoIdentityCredentials",
"sourceIp": "[redacted]",
"reason": "AUTHORIZATION_FAILURE",
"details": "Authorization Failure"
}
Note: AROASSNC65QVOYX3OXE6E matches the Role ID of
lava-cognito-authenticated-v2, confirming the correct role/session
was used for the attempt.
WHAT I HAVE ALREADY RULED OUT
[x] IAM permissions - AdministratorAccess attached; IAM Policy
Simulator confirms "allowed, N matching
statements" for iot:Connect on this
client ARN pattern
[x] Trust policy - manually verified against AWS docs,
"aud" matches Identity Pool ID exactly
[x] Identity Pool role - tested with two different IAM roles,
both created fresh, both fail identically
[x] Credential validity - sts:get-caller-identity succeeds
[x] STS regional activation - eu-central-1 shows "Active" under
IAM > Account settings > STS
[x] System clock skew - verified client and CloudWatch
timestamps match within seconds
[x] MQTT clientId format - tested both with the raw
"rfert-<IdentityId>" (containing ':')
and a sanitized version (':' replaced
with '-'); both fail identically. IAM
Resource was also tested with a full
wildcard ("client/*") -- no change.
[x] SigV4 signing bug on my end - independently reproduced with
AWS's own AWSIoTPythonSDK (official,
maintained library), not just my own
implementation
[x] General endpoint/account health - AWS Console's own MQTT test
client connects successfully to the
same endpoint using the console
session's own SigV4-signed WebSocket
connection
[x] AWS Organizations / SCP - this account is not a member of an
AWS Organization
[x] IoT Domain configuration - only the default "iot:Data-ATS" is
present, no custom domain configuration
Update: Also tried attaching a permissive AWS IoT Policy (iot:* on *) directly to the Cognito Identity ID via iot:AttachPolicy (in case MQTT-over-WebSocket IAM auth also requires this, as suggested in some older Cognito+IoT examples). Same AUTHORIZATION_FAILURE persists. This appears to rule out the missing-IoT-Policy hypothesis as well.
QUESTION FOR AWS SUPPORT
Given that credentials are valid (confirmed via STS) and IAM policy
evaluation allows the action (confirmed via Policy Simulator), why
would AWS IoT Core Device Gateway reject the MQTT CONNECT with
AUTHORIZATION_FAILURE specifically for credentials originating from a
Cognito Identity Pool AssumeRoleWithWebIdentity session, while
identical-style SigV4 WebSocket connections succeed via the Console's
own session credentials?
Is there an account-level or region-level configuration specific to
IoT Core's handling of Cognito Identity Pool-issued STS sessions that
we should check (e.g., an internal allow-list, a stale IoT Device
Gateway cache, or a known service-side issue)?
We are happy to provide additional CloudWatch log excerpts, trace IDs,
or attempt further reproduction steps as needed.