connect to AWS IoT using web socket with Cognito authenticated users
Asked Answered
O

6

18

I'm trying to connect to AWS IoT using web socket from the browser.

I've tried this example: https://github.com/awslabs/aws-iot-examples/tree/master/mqttSample

And another one a little bit modfied so it can be used with Cognito Identity pool logged users. https://github.com/dwyl/learn-aws-iot/blob/master/src/js/utils/request.js#L27

I can successfully connect if I use a IAM user with a valid IoT policy, but if I use the user credentials, I receive a "101 Switching Protocols" response but then it gets closed.

The IAM role associated with the authenticated user is correct, and I can sign requests and perform other private operations like calling APIG endpoints. Also the socket connection does not respond with 403. So it's likely not a permissions problem.

What else could it be?

Orna answered 28/10, 2016 at 8:47 Comment(0)
B
21

For unauthenticated cognito identities the "Identity pool anauthenticated" role is sufficient to allow connecting to the IoT MQTT broker. However for authenticated cognito identities two things are required:

  1. The "Identity pool authenticated" role must allow access to the IoT actions you require (e.g. connect, publish etc).

  2. You must attach an IoT policy (exactly like the ones that are attached to your devices) to the cognito identity using the AttachPrincipalPolicy API

Step 2 is where I was stuck earlier today as it was not particularly clear anywhere that this was required.

AFAIK there is no way to attach the IoT policy to a cognito user from any of the AWS web sites. However if you have the AWS command line interface setup on your machine you can do it from there. The command looks like:

aws iot attach-principal-policy --policy-name <iot-policy-name> --principal <cognito-identity-id>

The cognito identity id can be found using the Federated Identities > Your Pool > Identity browser or you could also find it in the responses to your CognitoIdentityCredentials.get call. It looks like this us-east-1:ba7cef62-f3eb-5be2-87e5-fffbdeed2824

For a production system you'll obviously want to automate attaching this policy, probably using a lambda function on user signup.

The section of the docs that talks about needing to attach the IoT policy can be found on this page:

For an authenticated Amazon Cognito identity to publish MQTT messages over HTTP on topic1 in your AWS account, you must specify two policies, as outlined here. The first policy must be attached to an Amazon Cognito identity pool role and allow identities from that pool to make a publish call. The second policy is attached to an Amazon Cognito user using the AWS IoT AttachPrincipalPolicy API and allows the specified Amazon Cognito user access to the topic1 topic.

Baxy answered 28/10, 2016 at 13:14 Comment(8)
I was missing the 2º. I've attached a new policy (created con aws iot console) with Action: ["iot:*"], Resource: "*" to each user on registration, and the same permissions (just for testing purposes) to the authenticated Cognito pool role. But now I get 403. I'm using the user identityId (us-east-1:...) as clientId in the Paho.MQTT.Client and region, AccessKeyId, and SecretKey to sign the request.Orna
@Orna Are you able to post a link to your project source? Or to a reproduction of the issue? In my project I'm using the AWS IoT Device SDK in order to connect to the message broker and handle device shadows. So I'll need see more of your source to see what your issue may be.Baxy
The problem was with the iot policy, which had an incorrect "resource", all is working now tyOrna
I'm using github.com/aws/aws-iot-device-sdk-js to get status of devices using sockets. I've attached the policy, and passing the cognito credential accesskey & secretkey to create connection the device, client.device = awsIot.device({ clientId: clientID, host: host, accessKeyId: cognitoCredentials.AccessKeyId, secretKey: cognitoCredentials.SessionToken, protocol: 'wss', sessionToken: cognitoCredentials.SessionToken }); But it getting 403 error. Am I missing something here?Brinna
@Indra, I just noticed your comment today. The accessKeyId, secretKey and sessionToken should be an empty string "" when you initialise AWSIoTData.thingShadow. You then need to call updateWebSocketCredentials on the object returned by thinkShadow passing in the following parameters `updateWebSocketCredentials(accessKeyId, secretKey, sessionToken).Baxy
@Indra why must they be an empty string when you initialise it? I've no idea, the sample code I found just had a comment stating that they needed to be. I've never had a chance to dig into why.Baxy
attach-principal-policy is marked in the docs as deprecated. Could you please show the alternative and correct way to attach the policy? Is the target simply as same as the principal?Fraze
@MohammedNoureldin, I am not sure as I haven't had to do this for awhile now. However the first thing I would try is using the identity ID as the target for the new AttachPolicy API.Baxy
N
7

In order to implement Caleb's answer on the front-end, I had to do a couple things:

  1. Create an IoT policy (named "default") by going to IoT Console > Security > Policies and copying and pasting the AWSIoTDataAccess policy contents into it
  2. Add the following inline policy to my Cognito Identity Pool's authenticated role: {"Effect": "Allow", "Action": ["iot:AttachPrincipalPolicy"], "Resource": ["*"]

Then I updated my front-end code to look like:

AWS.config.region = process.env.AWS_REGION;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: process.env.AWS_IDENTITY_POOL,
  Logins: {
    'graph.facebook.com': FACEBOOK_ACCESS_TOKEN
  }
});
AWS.config.credentials.get(() => {
  const IoT = new AWS.Iot();
  IoT.attachPrincipalPolicy({
    policyName: 'default',
    principal: AWS.config.credentials.identityId
  }, (err, res) => {
    if (err) console.error(err);
    // Connect to AWS IoT MQTT
  });
});
Nottingham answered 11/1, 2017 at 16:43 Comment(4)
You're a lifesaver dude.Aynat
I may be mistaken but this does not feel safe. You are allowing any of your authenticated users to decide which identities can perform which actions on IOT, inside the scope defined by the policy attached to the identity pool. So, if I understand correctly, any authenticated user would be able to assign himself or others a policy different from 'default'. It looks like this should be done by a lambda in the backend instead.Kiser
I haven't had a chance to test this, but I think you're right. For production purposes, think carefully about your policies and policy access from the front-end.Nottingham
Attaching the policy to the user should definitely not be done from the front end. Something on your backend needs to organise attaching the policy.Baxy
I
5

Here is a code sample to attach an IoT policy to a Cognito user id from a Lambda (NodeJS) function.

function attachPrincipalPolicy(device_id, cognito_user_id) {
    const iotMgmt = new AWS.Iot();
    return new Promise(function(resolve, reject) {
        let params = {
            policyName: device_id + '_policy',
            principal: cognito_user_id
        };
        console.log("Attaching IoT policy to Cognito principal")
        iotMgmt.attachPrincipalPolicy(params, (err, res) => {
            if (err) {
                console.error(err);
                reject(err);
            } else {
                resolve();
            }
        });
    });
}
Imprest answered 6/12, 2016 at 10:32 Comment(3)
Should this method necessarily be created as a separate Lambda function? I would like somehow to use this method inside my Dart code, but I am not able to figure out how or where to get this functionality.Fraze
This is an AWS API call, it can be made from anywhere.Corporeity
Thank you, yes since my comment I learned many new interesting things about this topic.Fraze
L
5

I refered to the answers of Caleb and senornestor, and the following implementation worked for me:

AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: AWSConfiguration.poolId,
  Logins: {
     'accounts.google.com': user.Zi.id_token
  }
});

var cognitoIdentity = new AWS.CognitoIdentity();

AWS.config.credentials.get(function(err, data) {
  if (!err) {
     console.log('retrieved identity: ' + AWS.config.credentials.identityId);

     var params = {
        IdentityId: AWS.config.credentials.identityId,
        Logins: {
           "accounts.google.com": user.Zi.id_token
        }
     };
     cognitoIdentity.getCredentialsForIdentity(params, function(err, data) {
        if (!err) {
           console.log('retrieved credentials');
           const IoT = new AWS.Iot();
           IoT.attachPrincipalPolicy({
              policyName: 'exampleIoTPolicy',
              principal: AWS.config.credentials.identityId
           }, (err, res) => {
              if (err) console.error(err);
           });  // Change the "policyName" to match your IoT Policy
        } else {
           console.log('error retrieving credentials: ' + err);
           alert('error retrieving credentials: ' + err);
        }
     });
  } else {
     console.log('error retrieving identity:' + err);
     alert('error retrieving identity: ' + err);
  }
});
Lou answered 20/6, 2017 at 11:29 Comment(0)
P
2

Here is an example application that should help demonstrate how to authenticate IoT with Cognito:

https://github.com/awslabs/aws-iot-chat-example

For explicit instructions, you can read:

https://github.com/awslabs/aws-iot-chat-example/blob/master/docs/authentication.md

Paterson answered 12/12, 2017 at 1:5 Comment(0)
F
1

It turns out, even in 2021, it is necessary to create a dedicated Lambda function that does the AttachPolicy (not AttachPrincipalPolicy because it is obsolete). As stated in the official Docs:

To attach an AWS IoT Core policy to a Amazon Cognito Identity, you must define a Lambda function that calls AttachPolicy.

The other answers showed how to implement that Lambda.

Fraze answered 23/5, 2021 at 20:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.