
AWS IoT Core MQTT with Cognito: Fixing AUTHORIZATION_FAILURE
Published on Sep 9, 2024
Table of Contents
- Application Architecture Overview
- Frontend: React Application
- Backend: Python Application using AWS CDK
- Deployment Context
- Setting Up AWS IoT Core MQTT with Cognito Identity Pools
- Common Issue: AUTHORIZATION_FAILURE with Authenticated Roles
- Step-by-Step Solution
- Retrieving the Cognito Identity ID
- Attaching the IoT Core Policy Using AWS Lambda
- Updating Permissions
- Validation and Testing
- Best Practices and Recommendations
- See This Architecture in Practice
- Conclusion
- Application Architecture Overview
- Frontend: React Application
- Backend: Python Application using AWS CDK
- Deployment Context
- Setting Up AWS IoT Core MQTT with Cognito Identity Pools
- Common Issue: AUTHORIZATION_FAILURE with Authenticated Roles
- Step-by-Step Solution
- Retrieving the Cognito Identity ID
- Attaching the IoT Core Policy Using AWS Lambda
- Updating Permissions
- Validation and Testing
- Best Practices and Recommendations
- See This Architecture in Practice
- Conclusion
Your React app has to react the moment a payment goes through, without making the user refresh the page. AWS IoT Core's MQTT broker can push that message to the browser, but Cognito Identity Pools add a catch.
Authenticated users can fail to connect with an AUTHORIZATION_FAILURE error even when the IAM role looks correct, while unauthenticated users connect without trouble. This guide explains why that happens and shows the fix.
Last updated: October 8, 2026
Short answer
An authenticated Cognito identity needs permissions in two places to connect to AWS IoT Core: the identity pool role and an AWS IoT policy attached to the identity itself. An unauthenticated identity needs only the role. The fix is to call attach_policy from a Lambda function once per login, using the identity ID from the authenticated request.
Application Architecture Overview
Frontend: React Application
Hosting: The React app is hosted on Amazon S3 and served through CloudFront for low latency and high availability.
AWS Amplify: The frontend uses AWS Amplify for sign-in through Cognito and for MQTT messaging through the PubSub category. In Amplify v6, PubSub ships as its own package, @aws-amplify/pubsub, and you should keep its version in line with aws-amplify.
Communication: The app talks to the backend over HTTPS.
Backend: Python Application using AWS CDK
API Gateway: Amazon API Gateway exposes the REST endpoints the frontend calls and acts as the entry point for every request from the React app.
Lambda Integrations: AWS Lambda functions process requests and run the backend logic. They scale automatically with demand and cost nothing when idle.
Real-Time Messaging: MQTT tells the frontend when something happens on the backend, such as a processed user payment. The page can then update dynamically, for example by removing a paywall as soon as the payment is confirmed.
Deployment Context
Several websites, mainly video streaming landing pages, are deployed with this architecture as part of the video on demand platform we built for Roligt AB. They all talk to the same backend, which keeps behavior consistent and avoids duplicated work. The full project is described in our Roligt AB VOD case study.
MQTT gives these sites instant feedback, such as payment confirmations, without polling the backend.
Setting Up AWS IoT Core MQTT with Cognito Identity Pools
A Cognito Identity Pool gives your users temporary AWS credentials based on the IAM role assigned to them. This setup uses two roles, one for authenticated users and one for unauthenticated visitors, and both are allowed to connect, subscribe and receive messages in AWS IoT Core.
Cognito Identity Pools: The pool maps each identity to a role. Both an authenticated and an unauthenticated role are configured in the pool.
Role permissions: Both roles need iot:Connect, iot:Subscribe and iot:Receive, plus iot:Publish if the browser sends messages. Amplify's documentation suggests the AWSIoTDataAccess and AWSIoTConfigAccess managed policies as a quick start. They are broad, so narrow them down before production. AWS recommends giving unauthenticated users access to very limited resources.
Authenticated vs. Unauthenticated Roles: An unauthenticated visitor is covered by the IAM policy on the identity pool role alone. A logged-in user, signed in through a Cognito User Pool, also needs an AWS IoT policy attached to their Cognito identity.
Common Issue: AUTHORIZATION_FAILURE with Authenticated Roles
The failure shows up as an AUTHORIZATION_FAILURE error when an authenticated user tries to connect, even though the role seems to have the right permissions. Unauthenticated users keep connecting normally.
Problem Analysis: For an authenticated identity, AWS IoT Core checks two places: the IAM policy on the identity pool role and an AWS IoT policy attached to the Cognito identity. If the second one is missing, the connection is rejected, however complete the role looks. AWS describes this on its Amazon Cognito identities page.
If the browser console shows AMQJS0008I Socket closed instead, check the role permissions first. Amplify's documentation lists that error as the symptom of missing IoT permissions on the authenticated role.
Step-by-Step Solution
The fix is to attach an AWS IoT policy to the authenticated user's Cognito identity. The steps below show how.
Retrieving the Cognito Identity ID
You need the user's Cognito Identity ID to attach the policy. With Amplify v6 you can read it from the auth session with fetchAuthSession from aws-amplify/auth, then pass it to your backend for the attach call.
Attaching the IoT Core Policy Using AWS Lambda
A Lambda function can attach the policy dynamically when the user logs in. It takes the Cognito Identity ID from the request and calls attach_policy through boto3 to link your AWS IoT policy to that identity.
import boto3
iot = boto3.client('iot')
def attach_iot_policy(event, context):
# With IAM authorization on API Gateway, you can read the caller's identity from
# event['requestContext']['identity']['cognitoIdentityId'] instead.
cognito_identity_id = event['identity_id']
policy_name = 'YourIoTPolicy'
try:
return iot.attach_policy(
policyName=policy_name,
target=cognito_identity_id
)
except iot.exceptions.ResourceAlreadyExistsException:
# The policy is already attached to this identity
return {'status': 'already attached'}A few things to note:
- identity_id must be the full Cognito Identity ID in the format region:uuid.
- The Lambda execution role needs the iot:AttachPolicy permission. iot:AttachPrincipalPolicy belongs to an older, deprecated API that attach_policy does not use. Without iot:AttachPolicy, the call fails with an access denied error even when the Cognito role permissions look correct.
- Call the function once per authenticated session, at login, and not on every MQTT connect attempt. AWS's own sample code ignores ResourceAlreadyExistsException for a policy that is already attached, and the Lambda above does the same.
- Read the identity ID from the authenticated request instead of trusting a value in the request body. That way a caller can only attach the policy to their own identity.
For reference, see the AWS boto3 IoT documentation, the AWS IoT Core policy documentation and AWS's guide to authorization with Amazon Cognito identities.
Updating Permissions
Make sure the Lambda execution role includes iot:AttachPolicy so the function can attach the policy to the identity. Without it the function fails, and the authorization errors continue.
Validation and Testing
Sign in as a test user, call the Lambda function, then connect with Amplify PubSub and subscribe to a test topic. Repeat the connection as an unauthenticated visitor to confirm both paths work. To check that the attachment worked, run aws iot list-attached-policies with the identity ID as the target.
Best Practices and Recommendations
Role Permissions Management: Review the permissions of each role carefully. Authenticated identities need an IoT policy attached directly to the identity, while unauthenticated identities rely on the role alone, so keep that role narrow.
Using AWS Amplify with PubSub: Amplify simplifies the MQTT connection, but you still manage identity roles and policies yourself, particularly when the same browser session moves between unauthenticated and authenticated states.
Scoping Topics per User: Use the ${cognito-identity.amazonaws.com:sub} policy variable in your IoT policy to limit each user to their own client ID and topics, for example arn:aws:iot:<region>:<account-id>:client/${cognito-identity.amazonaws.com:sub}. One shared policy then works for every identity.
Debugging Authorization Failures: Authorization failures usually point to missing permissions or a policy that was never attached. Check the role policies first, then confirm the attachment with aws iot list-attached-policies.
Policy Management: Use IAM and IoT policies to grant each role only what it needs. Narrow permissions reduce overhead and lower security risk.
See This Architecture in Practice
To see how Lambda, API Gateway, Cognito and Amplify fit together in a real production build, read our AI Mental Health Assistant MVP case study. Perfsys built a fully serverless AWS product that achieved over 90% cost reduction and delivered an investor-ready platform from scratch.
Conclusion
AWS IoT Core MQTT and Cognito work well together once you know that authenticated identities need their own IoT policy. The architecture in this guide (Lambda, API Gateway, Cognito and IoT Core) is one Perfsys deploys regularly for clients building event-driven, scalable products on AWS.
If you are planning something similar, our MVP Development Services are built for exactly this: serverless-first, AWS-native and ready to scale from day one.

Perfsys designs and builds event-driven AWS products, from Cognito sign-in to IoT Core messaging.
Ex-Software Engineer at Perfsys focused on building reliable, well-tested features across the product's frontend and backend.
AWS Experts, On-Demand
Need to move fast? Our cloud team is ready to scale, secure, and optimize your systems. Get serverless expertise, 24/7 support, and seamless CI/CD pipelines when you need it most.
Please accept cookies to load the booking widget.
