Skip to content

AWS IoT Core rejects MQTT-over-WebSocket (SigV4) CONNECT with AUTHORIZATION_FAILURE despite verified-correct IAM policy

0

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:
    1. A hand-written Dart implementation (SigV4 canonical request/signing verified line-by-line against AWS docs)
    2. 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

  1. Authenticate a Cognito User Pool user via USER_PASSWORD_AUTH (cognito-idp:InitiateAuth) -> obtain a valid ID token.

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

  3. Confirm credentials are valid and functional: sts:get-caller-identity -> SUCCESS, returns correct Account/Arn/UserId.

  4. 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).

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

2 Answers
1

Your CloudWatch logs expose the exact failure point: "principalId": "AROASSNC65QVOYX3OXE6E:CognitoIdentityCredentials" This confirms AWS IoT Core is evaluating your connection as a standard IAM Role session, not as a native Cognito Identity. Because the gateway sees the IAM Role, it completely ignores the AWS IoT Policy you attached to the Cognito Identity ID.

IAM permissions (even AdministratorAccess) are insufficient on their own. AWS IoT Core strictly requires an explicitly attached AWS IoT Policy mapped directly to the principal that is being evaluated.

Since IoT Core is authenticating the IAM Role, you must attach your AWS IoT Policy directly to the IAM Role ARN. Use the AttachPrincipalPolicy API instead of AttachPolicy:

aws iot attach-principal-policy \
  --policy-name <YourPermissiveIoTPolicyName> \
  --principal arn:aws:iam::[redacted]:role/lava-cognito-authenticated-v2

Alternative (Cognito Mapping Fix) If your architecture dictates that the policy must remain attached to the Cognito Identity ID, you need to switch your Identity Pool from the "Enhanced (Simplified)" flow to the "Basic (Classic)" flow. The Enhanced flow often omits the specific underlying claims in the STS token that the IoT Device Gateway relies on to map the generic role session back to the specific Cognito Identity ID.

Reference: AWS IoT Core Documentation - Attaching an AWS IoT policy to an IAM identity: https://docs.aws.amazon.com/iot/latest/developerguide/iam-users-groups-roles.html

EXPERT

answered a month ago

0

AWS IoT Core requires both IAM permissions and an attached AWS IoT Core policy for MQTT-over-WebSocket connections. Even if Cognito Identity Pool role has AdministratorAccess and STS credentials are valid, IoT Core will reject the CONNECT with AUTHORIZATION_FAILURE unless an IoT policy is explicitly attached to the Cognito identity or principal.

https://docs.aws.amazon.com/iot/latest/developerguide/iot-authorization.html

https://docs.aws.amazon.com/iot/latest/developerguide/iam-users-groups-roles.html

https://docs.aws.amazon.com/iot/latest/developerguide/cog-iot-policies.html

EXPERT

answered a month ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.