Alexa Account Linking with Cognito - amazon-web-services

You would think two of Amazon's products would integrate nicely together. You'd also think that Amazon would have proper documentation on their services. Both are horribly wrong.
I'm using Account Linking with Alexa and using AWS Cognito as my oauth provider. I am able to successfully link my account just fine, and with every subsequent alexa invocation I get an access token returned back to me. Now what?
I need to be able to access other AWS services such as dynamo, lambda, and iot from my alexa lambda function. Thought it would be as easy as something like this:
var accessToken = event.session.user.accessToken;
var params = {
IdentityPoolId: "{identity_pool_id}",
Logins : {
'cognito-idp.us-east-1.amazonaws.com/{user_pool_id}' : accessToken
}
};
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials(params);
AWS.config.credentials.get(function(err) {
if (err) {
console.log(err);
} else {
console.log('success');
console.log("Cognito Identity Id: " + AWS.config.credentials.identityId);
}
});
But of course it's not that simple. Can anyone help me out?

You need to assign appropriate "Policies" for the created "Role" under IAM. All of the AWS services works on policy based access permissions and these must be explicitly attached with the IAM role for that role to be able to access/run the underlying service on the behalf of that user role.
In summary you need to assign policies from IAM related to "IOT", "DynamoDB", "Lambda", etc.

Related

AWS Pinpoint updating user attributes when unauthenticated - security issue?

I'm looking at using AWS Pinpoint to send push notifications to my react native app. However it seems that unauthenticated users are able to update user attributes for any user they wish, ie there is no access control. I'm new to mobile development, but isn't putting stuff like that into the frontend a security issue? If it were a web application, people would be able to inspect network calls to obtain credentials and make any call they wish to updateEndpoint. Is this not applicable to mobile apps or am I misunderstanding something?
Details:
There's a step in the setup that says Edit the IAM policy document for unauthenticated identities to allow permissions for the mobiletargeting:PutEvents and mobiletargeting:UpdateEndpoint actions
And react native code snippet provided goes as follows:
import Analytics from '#aws-amplify/analytics';
import Auth from '#aws-amplify/auth';';
const amplifyConfig = {
Auth: {
identityPoolId: 'COGNITO_IDENTITY_POOL_ID',
region: 'ap-south-1'
}
}
//Initialize Amplify
Auth.configure(amplifyConfig);
const analyticsConfig = {
AWSPinpoint: {
// Amazon Pinpoint App Client ID
appId: 'cd73a57d200e49e2bc4b97d6ebf63cd4',
// Amazon service region
region: 'ap-south-1',
mandatorySignIn: false,
}
}
Analytics.configure(analyticsConfig)
Analytics.updateEndpoint({
attributes: {
interests: ['science', 'politics', 'travel'],
//..
},
userId: 'UserIdValue',
userAttributes: {
username: 'ilovethecloud'
}
});
I'm not sure if this will help,
But you have 2 IAM policies (in Cognito Identity-pool), 1 for authenticated users and 1 for unauth. users.
You should restrict the IAM policy for unauth users, so they can't edit other users info.
Also, the credentials that you are given by Cognito, are temporal, they expire (and get renewed by your react-native app), so in that way you are safe.

Which credentials to use in API Gateway's Lambda to access other AWS services

I have a Cognito User Pool and an API Gateway using Cognito User Pool authorizer. Each user in the pool is assigned to a group with an IAM role. With this setup at hand, the client authenticates with Cognito and then sends ID Token to the API Gateway, which authorizes the request and passes it to a Lambda function. The lambda then executes other services (DynamoDB and S3). My question is which credentials do I need to use in the lambda?
One option is to use ID Token passed to the lambda from API Gateway to get temporary credentials from STS. This basically allows me to use user's group IAM Role to access other AWS resources and would look as follows:
var credentials = new CognitoAWSCredentials(
"us-east-1:...", // Identity pool ID
RegionEndpoint.USEast1 // Region
);
credentials.AddLogin(
"cognito-idp.us-east-1.amazonaws.com/us-east-...", // User pool ID
"eyJraWQiOiJi..." // ID token
);
// use credentials to access AWS services
Another option is to use the execution role assigned to the Lambda function, but it almost feels like this is better suited for other scenarios... perhaps those where lambda is executed as part of an event? I also don't seem to understand how to get ahold of those credentials within lambda itself.
Is there something else I'm missing? What's a good approach to take here?
Update
What I had above was almost right, but not quite. First of all, figure out if your users will need unauthorized access. If so, go to your identity pool and enable unauthorized access.
To get access/secret keys for unauthorized users you'd do something like this (Using C# SDK, but the following concepts apply to other languages):
var client = new AmazonCognitoIdentityClient(
new AnonymousAWSCredentials(), // We're making public API calls
RegionEndpoint.USEast1); // Your Region
var identityId = await client.GetIdAsync(new GetIdRequest
{
AccountId = "****", // AWS account ID
IdentityPoolId = "us-east-1:****" // Identity Pool ID
});
var credentials = await client.GetCredentialsForIdentityAsync(new GetCredentialsForIdentityRequest()
{
IdentityId = identityId.IdentityId
});
// Use credentials.Credentials when calling AWS services
The above will issue credentials with permissions defined by the Unauthenticated IAM Role defined in your Identity Pool.
To get credentials for authenticated users you'll do this:
var client = new AmazonCognitoIdentityClient(
new AnonymousAWSCredentials(), // We're making public API calls
RegionEndpoint.USEast1); // Your region
var getIdentityIdRequest = new GetIdRequest
{
AccountId = "****", // AWS Account ID
IdentityPoolId = "us-east-1:****" // Identity Pool ID
};
getIdentityIdRequest.Logins.Add(
"cognito-idp.us-east-1.amazonaws.com/us-east-1****", // User Pool ID
idToken); // ID Token
var identityId = await client.GetIdAsync(getIdentityIdRequest);
var getCredentialsRequest = new GetCredentialsForIdentityRequest()
{
IdentityId = identityId.IdentityId
};
getCredentialsRequest.Logins.Add(
"cognito-idp.us-east-1.amazonaws.com/us-east-1****",
idToken);
var credentials = await client.GetCredentialsForIdentityAsync(getCredentialsRequest);
// Use credentials.Credentials when calling AWS services
You'll need the ID Token for this, so authenticate first and grab the ID token from the response. If the user holding the ID token belongs to a group in Cognito with an assigned IAM Role, the credentials returned from the above code will be for that role. If the user isn't assigned to a group or the group has no role, you'll get credentials for Authenticated User IAM Role assigned to the Identity Pool.
Also, as was suggested by #thomasmichaelwallace, the above can be done either from Lambda or from outside API Gateway/Lambda but only if you're using AWS_IAM Authorizer because it's the only one that accepts secret and access keys for authorization. If you're just using Cognito Authorizer, like i was, you'll need to pass ID token to your lambda and have your lambda gets credentials.
From your example, you're effectively using the "built-in" method for this (Cognito Federated Identities). Arguably you might take this even further and access your resources directly from your application using those credentials (rather than putting API-Gateway + Lambda in the way).
The execution role in Lambda is the role (credentials) that the lambda is invoked with (they do not need to be set, and no new CognitoAWSCredentials needs to be called by you). As you've suggested, this is to limit what a lambda can do (on a per-lambda basis, rather than the invoker).
If Cognito Federated Identities are working for you, then it makes sense for you to use them as they provide a way to use IAM to enforce your application's authorisation layer; and arguably mean that you're responsible for building less. Not all applications fit into this pattern, which is why AWS give you options.

How to delete a user from the user pool in the NodeJS lambda by admin

I faced a problem when a user has signed up but doesn't want to confirm his email. The solution is to delete an unconfirmed user from AWS Cognito.
So as I don't know his password, I am trying to write a Lambda function which I will trigger through API Gateway. This lambda should remove Cognito user.
I wrote this code but it doesn't work.
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({
apiVersion: '2016-03-18',
});
var params = {
UserPoolId: 'us-east-1_123456',
Username: 'user#mail.com' // I want to remove this user
};
cognitoidentityserviceprovider.adminDeleteUser(params, function (err, data) {
if (err) {
callback(err, err.stack);
} else {
callback(data);
}
});
I get an error:
user is not authorized to perform ...
Because of security, I don't want to set my admin credentials on frontend part and I want to do all work in this lambda... How to do it?
Any ideas?
Any solutions to prevent this problem?
You can assign a role to the lambda function and make a call to cognito api without passing any argument to the library you use to access aws services, that way the credential provider would fallback to the assumed role and have the lambda execution role's identity.
Usually roles are the way to go with amazon related authorizations.
Btw, this means that you have to create an iam role, a policy with the right cognito actions allowed and attach it to said role.
const cisp = new CognitoIdentityServiceProvider({ apiVersion: '2016-04-18' })
cisp.adminDeleteUser().promise() //delete current user as admin

connect to AWS IoT using web socket with Cognito authenticated users

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
});
});
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();
}
});
});
}
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 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
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.

AWS Lambda custom triggers

Can someone tell me how to get access to AWS credentials in an AWS Lambda function?
I've searched the internet thoroughly but still haven't found anything that has helped me.
Im writing the function in Java. I think I should have access to the credentials with the context object in the HandleRequest method.
If it helps, I want to invoke a DynamoDB client and upload a record to the database.
I came into the same problem myself recently.
This certainly is a blind spot in AWS's Lambda documentation for Java, in my opinion.
This snippet in Java should work for you, assuming you're using the AWS SDK for Java Document API :
DynamoDB dynamodb = new DynamoDB(
new AmazonDynamoDBClient(new EnvironmentVariableCredentialsProvider()));
The main takeaway is to use the EnvironmentVariableCredentialsProvider to access the required credentials to access your other AWS resources within the AWS Lambda container. The Lambda containers are shipped with credentials as environment variables, and this is sufficient in retrieving them.
Note: This creates a DynamoDB instance that only sees resources in the default region. To create one for a specific region, use this (assuming you want to access DynamoDB's in the ap-northeast-1 region):
DynamoDB dynamodb = new DynamoDB(
Regions.getRegion(Regions.AP_NORTHEAST_1).createClient(
AmazonDynamoDBClient.class,
new EnvironmentVariableCredentialsProvider(),
new ClientConfiguration()));
Your Lambda function's permissions are controlled by the IAM Role it executes as. Either add Dynamo PutItem permission to the current role, or create a new role for this purpose.
After giving permissions to the Role, you don't need to write special code to handle credentials, just use the AWS SDK normally. For example:
var AWS = require("aws-sdk");
exports.handler = function(event, context) {
var dynamodb = new AWS.DynamoDB();
var putItemParams = {
"TableName": "Scratch",
"Item": {
"Id": {
"S": "foo"
},
"Text": {
"S": "bar"
}
}
};
dynamodb.putItem(putItemParams, function (err, response) {
if (err) {
context.fail("dynamodb.putItem failed: " + err);
} else {
context.succeed("dynamodb.putItem succeeded");
}
});
};
Is sufficient to put an item in a DynamoDB table, with the correct Role permissions.
Adding to #Gordon Tai's answer, using the current api using AmazonDynamoDBClientBuilder this looks like:
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withCredentials(new EnvironmentVariableCredentialsProvider())
.withRegion(Regions.US_EAST_1)
.build();
DynamoDB dynamoDB = new DynamoDB(client);