Reputation: 10757
Shortly: I have a Federated Identity Pool which has roles for both unauthenticated and authenticated access. I have no problems with unauthenticated access. But when it comes to authenticated access, the user logs in just fine, but my role for the authenticated users does not get actually applied.
I have an s3 bucket with simple index.html and index.js files which communicate via MQTT.
Both policies for authenticated and unauthenticated users look exactly the same right now, and are fairly permissive (of course it's not the way to go for production, but I'm just trying to make it work in any way so far). So, both policies look as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
And in index.js I can establish MQTT connection as an unauthenticated user, as follows:
var region = 'eu-west-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'my-identity-pool-id',
});
AWS.config.credentials.clearCachedId();
AWS.config.credentials.get(function(err) {
console.log('accessKeyId:', AWS.config.credentials.accessKeyId);
if(err) {
console.log(err);
return;
}
var requestUrl = SigV4Utils.getSignedUrl(
'wss',
'data.iot.' + region + '.amazonaws.com',
'/mqtt',
'iotdevicegateway',
region,
AWS.config.credentials.accessKeyId,
AWS.config.credentials.secretAccessKey,
AWS.config.credentials.sessionToken
);
initClient(requestUrl);
});
initClient()
just establishes MQTT connection by means of Paho
:
function initClient(requestUrl) {
var clientId = String(Math.random()).replace('.', '');
var rpcId = "smart_heater_" + String(Math.random()).replace('.', '');
var client = new Paho.MQTT.Client(requestUrl, clientId);
var connectOptions = {
onSuccess: function () {
console.log('connected');
// Now I can call client.subscribe(...) or client.send(...)
},
useSSL: true,
timeout: 3,
mqttVersion: 4,
onFailure: function (err) {
console.error('connect failed', err);
}
};
client.connect(connectOptions);
client.onMessageArrived = function (message) {
console.log("msg arrived: " + message);
};
}
And it works just fine: connected
gets printed to the console, and I can actually send/subscribe.
Now, I'm trying to do the same for authenticated users. For that, I added Cognito User Pool, created a user there, created an "app", added Authentication provider "Cognito" to my Identity Pool (with appropriate User Pool ID and App Client ID), and the authentication itself works just fine: user gets logged in. Code looks as follows:
var region = 'eu-west-1';
var poolData = {
UserPoolId: 'eu-west-1_XXXXXXXXX',
ClientId: 'ZZZZZZZZZZZZZZZZZZZZZZZZZ',
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationData = {
Username: 'myusername',
Password: 'mypassword',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: 'myusername',
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('result:', result);
console.log('access token: ' + result.getAccessToken().getJwtToken());
var myUserPoolId = 'eu-west-1_XXXXXXXXX';
console.log('You are now logged in.');
// Add the User's Id Token to the Cognito credentials login map.
var logins = {};
logins['cognito-idp.' + region + '.amazonaws.com/' + myUserPoolId] = result.getIdToken().getJwtToken();
AWS.config.credentials.params.Logins = logins;
// finally, expire the credentials so we refresh on the next request
AWS.config.credentials.expired = true;
//call refresh method in order to authenticate user and get new temp credentials
AWS.config.credentials.refresh((error) => {
if (error) {
console.error(error);
} else {
console.log('Successfully logged!');
console.log('accessKeyId:', AWS.config.credentials.accessKeyId);
var requestUrl = SigV4Utils.getSignedUrl(
'wss',
'data.iot.' + region + '.amazonaws.com',
'/mqtt',
'iotdevicegateway',
region,
AWS.config.credentials.accessKeyId,
AWS.config.credentials.secretAccessKey,
AWS.config.credentials.sessionToken
);
initClient(requestUrl);
}
});
},
onFailure: function (err) {
alert(err);
},
newPasswordRequired: function(userAttributes, requiredAttributes) {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// the api doesn't accept this field back
delete userAttributes.email_verified;
var newPassword = prompt('Enter new password ', '');
// Get these details and call
cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
},
});
The logging-in part wasn't trivial to get working, but now it works fine: in the console, I see:
You are now logged in.
Successfully logged!
accessKeyId: ASIAIRV4HOMOH6DXFTWA
And then, at the connection time, I'm getting the following error:
connect failed: {invocationContext: undefined, errorCode: 8, errorMessage: "AMQJS0008I Socket closed."}
I assume this is because the role, which is actually applied to the authenticated user, does not allow the action iot:Connect
; I believe so because I can reproduce exactly the same error for unauthenticated users, if I put the following in the policy for unauthenticated users:
{
"Action": [
"iot:Connect"
],
"Resource": "*",
"Effect": "Deny"
},
Then unauthenticated users will receive the same error AMQJS0008I Socket closed
when they try to connect.
So, it looks like my policy for authenticated users does not actually get applied to the authenticated user.
I experimented a lot before writing this question. Currently, in my Identity Pool settings, in "Authenticated role selection" I have just "Use default role", which should indeed pick my authenticated role, which is:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
But I also tried to select "Choose role with rules" and write a rule which would set my role based on some matching clause.
I also tried to create a group in my User Pool, use the same role there, add my user to this group, and in the Identity Pool settings set "Choose role from token".
Nothing helped. I keep getting this errorMessage: "AMQJS0008I Socket closed."
message for authenticated users, whereas for unauthenticated users everything works just fine, even though the policies are identical.
Any help is appreciated.
Upvotes: 3
Views: 1716
Reputation: 10757
The problem was that, for authenticated Cognito users, attaching IAM policy to the Identity Pool is not enough: in addition to that, one must attach an IoT policy (not IAM policy) to each identity (basically, to each user), like this:
$ aws iot attach-principal-policy \
--policy-name Some-Policy \
--principal us-east-1:0390875e-98ef-420d-a52d-f4188ce3cf06
Check also this thread https://forums.aws.amazon.com/thread.jspa?messageID=726121
Upvotes: 3