Skip to content

AWS IoT MQTT-over-WebSockets handshake returns 403 ForbiddenException for valid SigV4 with Admin IAM (us-east-2)

0

Hi Everyone,

I'm hitting a wall with AWS IoT MQTT-over-WebSockets and would love a second pair of eyes. Every WSS handshake against my account's IoT data endpoint comes back with HTTP 403 Forbidden and the body {"message":"Forbidden","traceId":"..."}. The header is x-amzn-ErrorType: ForbiddenException. This happens for every caller I've tried, including my own root-account IAM user that has AdministratorAccess attached. Because the WS upgrade never succeeds, the broker never sees the CONNECT packet, so I get nothing in CloudWatch v2 logs to look at.

Endpoint: aiqroq3b5zlot-ats.iot.us-east-2.amazonaws.com (returned by aws iot describe-endpoint --endpoint-type iot:Data-ATS) Region: us-east-2

The weird part is that REST publishing works fine with the same credentials. aws iot-data publish to the same endpoint succeeds and lands a Publish-In Success line in AWSIotLogsV2 every time. So the credentials, the endpoint, the network path - all good for the data plane. Only the WSS handshake on /mqtt is rejected. A plain HTTPS GET on /mqtt with the same presigned URL returns 404 (which is expected since /mqtt is upgrade-only), so I know SigV4 is being validated correctly - it's not a signature problem.

Things I've ruled out:

IAM - aws iam simulate-principal-policy says iot:Connect is Allowed for both my admin user and a Cognito-Identity-Pool-authenticated identity, matched by AdministratorAccess in one case and a permissive inline policy in the other.

AWS IoT policy - I attached an IoT policy with iot:* on * to the Cognito identity. aws iot list-attached-policies --target <identityId> confirms it's there. Same 403.

Custom authorizer - aws iot describe-default-authorizer returns ResourceNotFoundException. aws iot list-authorizers returns an empty array.

Domain configurations - only the default iot:Data-ATS and iot:CredentialProvider are present.

SCP / permissions boundary - REST publish works with the same role, so nothing in the permission tree is blocking iot:* wholesale.

Signing code - I reproduced this from CloudShell using boto3 with my admin credentials, completely bypassing my app's SigV4 code. Same 403. So it isn't a bug in my client signing.

Header variations - I tried Sec-WebSocket-Protocol with mqtt, mqttv3.1.1, and no value. With and without the Origin header. Both the well-known sample Sec-WebSocket-Key and a fresh openssl rand -base64 16 value. All combinations produce the same 403.

Reproduction (CloudShell, admin user):

A short Python script using boto3 produces a SigV4 presigned URL for service iotdevicegateway, region us-east-2, on https://<endpoint>/mqtt with X-Amz-SignedHeaders=host. Then curl -i against that URL with Connection: Upgrade, Upgrade: websocket, Sec-WebSocket-Version: 13, a random Sec-WebSocket-Key, and Sec-WebSocket-Protocol: mqtt returns 403 every time.

Here are some traceIds from my recent failed handshakes if anyone from the IoT team can pull the broker-side denial reason:

8fd55d51-d4f2-dd1e-973a-02d33a34cca2 (admin user, random WS key, around 2026-04-30 08:06 UTC) f85c44e4-5191-d615-02e3-f99479b6213a (admin user, around 2026-04-30 07:53 UTC) 7d192525-1db5-564e-350a-13f894d79037 (Cognito identity with permissive IoT policy and permissive IAM policy, around 2026-04-30 07:29 UTC) 57c89eb6-c80f-09bb-625c-461809aa6844 (same Cognito identity, mqttv3.1.1 subprotocol, around 2026-04-30 07:47 UTC) v2 logging is set up correctly - aws iot get-v2-logging-options shows a roleArn, defaultLogLevel DEBUG, disableAllLogs false. I can see Publish-In events from the REST publishes, so the logging pipe works. I just never see any Connect events, which lines up with the broker never being reached.

So my question: with no custom authorizer, no SCP block, valid SigV4, and iot:Connect allowed by IAM, what could be causing iotdevicegateway to return 403 ForbiddenException on the WS upgrade specifically? Is there an account-level switch or hidden state I'm missing? Has anyone seen this pattern before?

Happy to provide more traceIds or run any specific test that helps narrow it down.

Thank you!

asked 18 days ago42 views
1 Answer
0

Hello.

It may not be directly related, but it seems that in some cases, a 403 error can occur due to problems with the device's time synchronization.
https://stackoverflow.com/questions/43024822/aws-iot-websocket-connection-returns-a-403

EXPERT
answered 17 days 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.