The Internet of Things on AWS – Official Blog

Configuring Cognito User Pools to Communicate with AWS IoT Core

AWS IoT Core supports certificate-based mutual authentication, custom authorizers, and Amazon Cognito Identity as way to authenticate requests to the AWS IoT device gateway. Amazon Cognito User Pools was made generally available last year. It allows customers to easily add user sign up and sign in to mobile and web apps. You can use Cognito User Pools identities to talk to AWS IoT Core by linking them to Cognito Federated Identities.

In this blog post, I will walk you through how to set up Amazon Cognito User Pools with Amazon Cognito Federated Identities to talk to AWS IoT Core using an authenticated Amazon Cognito identity over WebSocket. I assume you are familiar with the basic components and functionality of AWS IoT and Cognito.

To create a user pool

Sign in to the AWS Management Console and navigate to Amazon Cognito.
In the Amazon Cognito console, choose Manage User Pool, and then choose Create a user pool.
For Pool name, enter demo-pool.
Make a note of the user pool ID. You’ll need it later.

To create an app client

From the navigation pane, choose App clients, and then choose Add an app client.
For App client name, enter demo-app-client.
Make a note of the app client ID. You’ll need it later.

Clear the Generate client secret check box.
Review the demo pool settings, and then choose Create app client.

To create an identity pool

Next, create a federated identity pool using Amazon Cognito User Pools as the identity provider. Use the user pool ID and app client ID created in the previous steps.

In the Amazon Cognito console, choose Federated Identities.
Create an identity pool and name it demo identity pool.
For authentication provider, choose Cognito.
Enter the user pool ID. (You can find the ID under User Pools → demo-pool → General Settings → Pool ID)

Enter the app client ID. (You can find it under User Pools → demo-pool → App Integration → App client settings → demo-app-client→ ID)

Finally, create an identity pool that looks like the following:

Choose Create Pool.

Note: Two IAM roles will be created for you: one for an authenticated pool and another for an unauthenticated pool. In this post, we will use the authenticated pool only.

To grant AWS IoT permission to the Amazon Cognito identity pool

Each unique user who signs in receives a unique identity provided by the Cognito Identity authenticated pool. The authenticated pool policy applies to that identity, so make sure you add AWS IoT-specific permission to the IAM role policy for the authenticated pool.
In the Action for the allowed statement, add “iot:Connect”, “iot:Publish”, “iot:Subscribe”, “iot:Receive”, “iot:GetThingShadow”
and “iot:AttachPrincipalPolicy.” I’m adding the “iot:AttachPrincipalPolicy” for demonstration purposes only. You should not use it in production.

To create an AWS IoT policy for Amazon Cognito identities

An Amazon Cognito authenticated user needs two policies to access AWS IoT. The first policy is attached to the role of the authenticated pool to authenticate and authorize the Cognito user to communicate with AWS IoT. The second policy is attached to the authenticated Cognito user ID principal for fine-grained permissions.

Sign in to AWS IoT console.
From the navigation pane, choose Secure , and then choose Policies.
On the Create a policy page, for Name, enter demo-policy.
In Add statements, for Action, enter iot:Connect. For Resource ARN, enter *.
Add another statement. For Action, enter iot:Publish. For Resource ARN, enter the topic name (demoTopic) at the end of resource ARN.

Choose Create.

To register the user and get the Cognito Identity ID

In this blog post, I am using a JavaScript example from the amazon cognito auth JS GitHub repository.

To complete the app client setup for the demo, go back to the App client settings page in the Amazon Cognito console and grant permission to request code and choose allowed scopes. To set up redirect URLs, we’ll use localhost.

For Callback URL(s) and Sign out URL(s), enter http://localhost:8000.
Under Allowed OAuth Flows, select Authorization code grant and Implicit grant.
Under Allowed OAuth Scopes, select email and openid.


Choose Save changes.

Get the domain name for your app client.
In the Amazon Cognito console, under App Integration, choose Domain name.
Enter a domain name for your app (for example, iot–).
Choose Check availability.


Choose Save changes.

Sample Code to Get Cognito Identity Credentials

Because the AWS IoT JavaScript SDK is in Node.js, it won’t run directly in the browser. For this demo, I use Browserify to convert the SDK into browser-supported JavaScript.

Install the AWS IoT JavaScript SDK.
Create a file named AWSIotDeviceSdk.js with the following contents:

var AwsIot = require('aws-iot-device-sdk');
window.AwsIot = AwsIot; // make it global

Install Browserify to make the demo to work with a browser.
Run the following command in a terminal to browserify AWSIotDeviceSdk.js to bundle.js.

terminal> browserify path/to/AWSIotDeviceSdk.js -o bundle.js

Download the latest version of the AWS JavaScript SDK.
Download this sample from amazon-cognito-auth-js.
In the sample’s index.html, replace the sample code’s initCognitoSDK JavaScript function as shown. Use your values to request an identity. Make sure you import the aws sdk js and bundle.js as shown in the code.

    <script src="/path/to/aws-sdk.js"/>
    <script src="/path/to/bundle.js"/>
    <script type="text/javascript">
      function initCognitoSDK() {
        AWS.config.region = '<REGION>'; // like: us-east-1
        var authData = {
            ClientId: '<YOUR_CLIENT_ID>', // Your client id here
            AppWebDomain: '<YOUR APP WEB DOMAIN>',
            TokenScopesArray: ['email', 'openid'], // e.g.['phone', 'email', 'profile','openid', 'aws.cognito.signin.user.admin'],
            RedirectUriSignIn: '<REDIRECT_SIGN_IN_URI>',
            RedirectUriSignOut: '<REDIRECT_SIGN_OUT_URI>',
            UserPoolId: '<USER_POOL_ID>', // Your user pool id here
        };
        var login = {};
        var auth = new AmazonCognitoIdentity.CognitoAuth(authData);
        auth.userhandler = {
            onSuccess: function (result) {
                //alert("Sign in success");
                showSignedIn(result);
                var loginKey = 'cognito-idp.' + AWS.config.region + '.amazonaws.com/' + authData['UserPoolId'];
                login[loginKey] = result.getIdToken().getJwtToken();
                AWS.config.credentials = new AWS.CognitoIdentityCredentials({
                    IdentityPoolId: '<COGNITO_IDENTITY_POOL_ID>',
                    Logins: login
                });
                AWS.config.credentials.refresh((error) => {
                    if (error) {
                        console.error(error);
                    } else {
                        var principal = AWS.config.credentials.identityId;
                        console.log("IdentityId: " + principal);

                        //Now we have cognito identity and credentials to make AWS IoT calls.
                        //Attach pre-created IoT policy to this principal. 
                        //IMPORTANT: Make sure you have granted AttachPrincipalPolicy API permission in IAM to Cognito Identity Pool's Role.
                        //It is done here for the demo purpose only while cognito user should NOT be allowed to call AttachPrincipalPolicy in production, this step must be done by Administrator only
                        attachPrincipalPolicy("demo-policy", principal);

                        //Now we can use this principal for IoT actions
                        //We'll need aws-iot-device-sdk-js for mqtt over websocket calls using these cognito credentials.
                        connect();
                    }
                });
            },
            onFailure: function (err) {
                alert("Error!");
            }
        };
        return auth;
      }

      //Need aws-sdk.js to work
      function attachPrincipalPolicy(policyName, principal) {
          new AWS.Iot().attachPrincipalPolicy({ policyName: policyName, principal: principal }, function (err, data) {
            if (err) {
                    console.error(err); // an error occurred
                }
          });
       }
       
       //Need bundle.js to work
       function connect() {
            var clientID = 'webapp:' + new Date().getTime(); //needs to be unique
            device = AwsIot.device({
                clientId: clientID,
                host: '<YOUR_AWS_IOT_ENDPOINT>', //can be queried using 'aws iot describe-endpoint'
                protocol: 'wss',
                accessKeyId: AWS.config.credentials.accessKeyId,
                secretKey: AWS.config.credentials.secretAccessKey,
                sessionToken: AWS.config.credentials.sessionToken
            });
            device.on('connect', function () {
                console.log("connected")
                publishMessage(device, 'demoTopic', 'demo success!')
            });
            function publishMessage(device, topic, msg) {
                device.publish(topic, msg, { qos: 1 }, function (err) {
                    if (err) {
                        alert("failed to publish iot message!");
                        console.error(err);
                    } else {
                        console.log("published to demoTopic");
                        showMessage("Message Published", "Topic: " + topic, "Message: " + msg);
                    }
                });
            }
       }
    </script>

Now we are ready to send MQTT messages over WebSocket to AWS IoT using Amazon Cognito Identity credentials.

Run the local server to receive credentials back to the localhost callback URL, as defined in the app client settings.

terminal> cd path/to/cognito/sample
terminal> python3 -m http.server

You should get output like ‘Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) …’

Type http://localhost:8000/ in your browser to load our modified index.html from sample.

Choose Sign In to create a user or sign in with an existing one.

After you are signed in, you should see a message published to the AWS IoT demoTopic notification, as shown here.

We can also subscribe to the demoTopic from the AWS IoT console to see if this worked.
In the navigation pane, choose Test.
Under Subscribe to a topic, enter demoTopic, and then choose Subscribe to topic.

Try refreshing the index.html page to publish the message again using the authenticated Cognito credentials. You should receive a message on the AWS IoT console.

Conclusion

In this blog post, I have shown you how to configure Amazon Cognito User Pools with Amazon Cognito Federated Identities and use authenticated Cognito Identity credentials to publish MQTT message over WebSocket to the AWS IoT Core message broker.

Further Reading

Cognito Federated Login
Cognito User Pools
AWS IoT Core Authentication with Cognito

Gitesh Tyagi