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?
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:
The "Identity pool authenticated" role must allow access to the IoT actions you require (e.g. connect, publish etc).
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.
In order to implement Caleb's answer on the front-end, I had to do a couple things:
- Create an IoT policy (named "default") by going to IoT Console > Security > Policies and copying and pasting the
AWSIoTDataAccess
policy contents into it
- 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
});
});
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);
}
});
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();
}
});
});
}
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