AWS Cognito Groups and AWS Api Gateway - amazon-web-services

I am starting with serverless on AWS, and I am using AWS Cognito for user authentication and authorization. For what I saw on the documentation and examples out there, you can make groups for allowing certain users to be able to use an Api Gateway endpoint, attaching a role and a policy to that group. I try this, and then made a simple client and try with two different users, and both are able to get a 200 status code instead of one of them getting that it is unauthorize. For creating the role I went to IAM, create role, role for identity provider access, grant access to web identity providers, and then I choose Amazon Cognito and choose my user pool of Cognito.
Trust Relationship:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1_8TAUVKbGP"
}
}
}
]
}
Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"my-arn-resourse-from-api-gateway"
]
}
]
}
Then I assigned this role to my admin group and add a user to that group, which should allow access to that Api Gateway resource by attaching that policy to the user when it signs in. But when I try with a user not in that group it still works. By the way, on my Api Gateway resource in the request I put for authorization my cognito pool.
Thanks very much!

Ok finally I got it to work perfectly! The problem was in my IAM Role, in the trust relationship document
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "identity_pool_id"
},
In that part instead of using my Identity Pool Id, I was using my User Pool Id. Once that was fixed, I got the credentials back, and tried in Postman using those credentials and it worked perfectly. Then I change the same user to another role, and access was denied as planned! The most importante part for using role authorization on an easy way, is as agent420 said to use AWS_IAM method for securing the api, then the rest is handle by aws.

You would need to use the AWS_IAM method instead for your use case. Also, in this case all your API requests would need to be SIGv4 signed. You can use Postman (chrome extension) for testing. It includes an option for AWS credentials.

I try what you said! I think I am in the right way but AWS.config.credentials is returning sessionToken, and accessKeyId both with null. This is the code I am using:
let poolData = {
UserPoolId : 'my_user_pool_id',
ClientId : 'my_app_client_id'
};
let authenticationData = {
Username : 'username',
Password : 'password',
};
let userPool = new CognitoUserPool(poolData);
let userData = {
Username : 'username',
Pool : userPool
};
let authenticationDetails = new AuthenticationDetails(authenticationData);
let cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
console.log(result);
AWS.config.region = 'my_region';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId : 'my_identity_pool_id',
Logins : {
'cognito-idp.my_region.amazonaws.com/my_user_pool_id' : result.getIdToken().getJwtToken()
}
});
console.log(AWS.config.credentials);
},
onFailure: (error) => {
}
});
The result from the authenticateUser returns the expected tokens. The problem I think is when retrieving the CognitoIdentityCredentials.
Thanks very much!

Related

AWS cognito identity pool ABAC how to map custom multi-valued attributes?

Example open id token from the identity provider (Cognito user pool in this example):
{
"cognito:groups": [
"testers",
"admins",
],
"email_verified": false,
...
}
I want to use ABAC, like the example given here: https://docs.aws.amazon.com/cognito/latest/developerguide/using-attributes-for-access-control-policy-example.html
So that I can add policy statements to the role associated with the Cognito identity pool with conditions, example:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:eu-west-1:123456789012:dbuser:cluster-teapot/db-user",
"Effect": "Allow",
"Condition": {
"ForAnyValue:StringLike": {
"aws:PrincipalTag/groups": "admins"
}
}
}
]
}
It works like a charm if you use single valued custom claims, like the "email_verified" above, but if I try to map array-valued claims like "cognito:groups"
aws cognito-identity get-credentials-for-identity...
fails with: Invalid identity pool configuration. Check assigned IAM roles for this pool.
This is not a problem with trust since its tried and tested with single-valued claims, so I am wondering if someone knows if the syntax is wrong, or if this is a missing feature?

How can I listUsers in Cognito from another account

Try to use cognitoIdentityService.listUsers with a role running ec2 machine and it works great in the local org (orgA).
I also want the same role to listUser in another org (orgB).
I created a a role with trust in the orgB with orgA in the Trust relationship and gave it Cognito permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::xxxxxxxxxxxx:root"
},
"Action": "sts:AssumeRole"
}
]
}
Then in orgA i added an inline policy to the role i want to give access
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::xxxxxxxxxxxx:role/orgBCognitoAccess"
}
}
I pass the REGION and USERPOOLID to my function.
const params = {
"UserPoolId": UserPoolId,
"Filter": `email = \"${email}\"`,
"Limit": 1
}
cognitoIdentityService.listUsers(params, (err, data) => {
But all i get in return is an error. Should it be switch roles when it sees the userpool does not exist? Is that logic I have to write in or can it be done at all?
{"message":"User pool ca-central-1_BBBBBBBB does not exist.","code":"ResourceNotFoundException","time":"2021-01-18T18:01:29.279Z","requestId":"acc14424-9f1d-411a-95f2-1a302e5773f8","statusCode":400,"retryable":false,"retryDelay":21.27495199615266}
Thanks
Yes, you have given permissions and everything to the roles, but you are still using role in OrgA, so to make the API call you to have to switch role in OrgB and use those credentials for making the client and eventually make the call.
sts_client = boto3.client('sts')
# Call the assume_role method of the STSConnection object and pass the role
# ARN and a role session name.
assumed_role_object=sts_client.assume_role(
RoleArn="arn:aws:iam::account-of-role-to-assume:role/name-of-role",
RoleSessionName="AssumeRoleSession1"
)
# From the response that contains the assumed role, get the temporary
# credentials that can be used to make subsequent API calls
credentials=assumed_role_object['Credentials']
# Use the temporary credentials that AssumeRole returns to make a
# connection to Amazon S3
s3_resource=boto3.resource(
's3',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken'],
)
# Use the Amazon S3 resource object that is now configured with the
# credentials to access your S3 buckets.
for bucket in s3_resource.buckets.all():
print(bucket.name)
you can find more details in the documentation
To verify this during the testing you can make GetCallerIdentity this gives you enough details.
{
"UserId": "AIDASAMPLEUSERID",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/DevAdmin"
}

How do I make an AWS AppSync GraphQL API publicly accessible while using Amazon Cognito for authentication?

Currently I am using Amazon Cognito for authentication in an AWS Amplify project, so only signed-in users have access to the api.
But I want to have some api calls publicly accessible.
How do I go about this?
I just solved this exactly same problem. This is what I did:
Update your API by running amplify update auth and select IAM as your users handler (everything else go with default)
Login to your AWS console -> Appsync and modify access to IAM (instead of Cognito Pool)
Go to the IAM console and create IAM policies for both AUTH and UNAUTH users (search them on the list by typing the name of your Appsync app)
Locate the AUTH user and attach the following policy (update it with your info):
AUTH USER
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/*"
]
}
]
}
Locate the unauth user and attach the following Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/types/Query/fields/<your Query name>",
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/types/Query/fields/<your Query name>",
"arn:aws:appsync:<AWS region>:<AWS account ID>:apis/<app sync endpoint ID>/types/Query/fields/<your Query name>"
]
}
]
}
And now the thing that is not documented (people transitioning from Cognito Pools to IAM ) You need to import {AUTH_TYPE}
import AWSAppSyncClient, {AUTH_TYPE} from "aws-appsync";
and use it to load the credentials in the AppSync initialization
const client = new AWSAppSyncClient(
{
disableOffline: true,
url: aws_config.aws_appsync_graphqlEndpoint,
region: aws_config.aws_cognito_region,
auth: {
// IAM
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials(),
});
Hope this helps.
For AppSync APIs - API Keys are considered "unauthenticated"
See the below documentation:
https://docs.aws.amazon.com/appsync/latest/devguide/security.html#api-key-authorization

How to use DynamoDB fine grained access control with Cognito User Pools?

I'm having trouble understanding how to use fine-grained access control on DynamoDB when logged in using Cognito User Pools. I've followed the docs and googled around, but for some reason I can't seem to get it working.
My AWS setup is listed below. If I remove the condition in the role policy, I can get and put items no problem, so it seems likely that the condition is the problem. But I can't figure out how or where to debug policies that depend on authenticated identities - what variables are available, what are their values, etc etc.
Any help would be greatly appreciated!
DynamoDB table
Table name: documents
Primary partition key: userID (String)
Primary sort key: docID (String)
DynamoDB example row
{
"attributes": {},
"docID": "0f332745-f749-4b1a-b26d-4593959e9847",
"lastModifiedNumeric": 1470175027561,
"lastModifiedText": "Wed Aug 03 2016 07:57:07 GMT+1000 (AEST)",
"type": "documents",
"userID": "4fbf0c06-03a9-4cbe-b45c-ca4cd0f5f3cb"
}
Cognito User Pool User
User Status: Enabled / Confirmed
MFA Status: Disabled
sub: 4fbf0c06-03a9-4cbe-b45c-ca4cd0f5f3cb
email_verified: true
Role policy for "RoleName"
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": [
"arn:aws:dynamodb:ap-southeast-2:NUMBER:table/documents"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${cognito-identity.amazonaws.com:sub}"
]
}
}
}
]
}
Login information returned from cognitoUser.getUserAttributes()
attribute sub has value 4fbf0c06-03a9-4cbe-b45c-ca4cd0f5f3cb
attribute email_verified has value true
attribute email has value ****#****com
Error message
Code: "AccessDeniedException"
Message: User: arn:aws:sts::NUMBER:assumed-role/ROLE_NAME/CognitoIdentityCredentials is not authorized to perform: dynamodb:GetItem on resource: arn:aws:dynamodb:ap-southeast-2:NUMBER:table/documents
The policy variable "${cognito-identity.amazonaws.com:sub}" is not the user sub which you get from Cognito user pools. It is in fact the identity id of a user which is generated by the Cognito Federated Identity service when you federate a user from Cognito User Pools with Federated identity service.
Since, the value in "${cognito-identity.amazonaws.com:sub}" never matches what you have in your DynamoDB row, it fails with AccessDenied. For this to work, the userId in your Dynamo entry should actually be the identity id, not sub. Currently, there is no direct link between IAM policy variables and Cognito User Pools service.
Here are some doc links which might help.
1. IAM roles with Cognito Federated Identity Service
2. Integrating User Pools with Cognito Federated Identity Service

Cognito Role and AWS S3 Bucket policy for mobile and web access

Our goal is to create S3 bucket and IAM role policies that will only allow S3 access to our logged in users.
We are hosting private files on an S3 bucket that will be accessed from both a web and mobile app. We are attempting to add a layer of security with Amazon Cognito, using an unauthenticated role, so that any users signed into our application can access the S3 bucket.
Using the AWS-SDK for JS and following the basic AWS.config.credentials setup, we can see 1 identity accessed and the number of syncs in our Amazon Cognito Identity Dashboard. Since we seem to be connecting to the IdentityPool, I'm thinking that our policies may need some tweaking so that logged in users that have the unauthenticated Cognito role can access the S3 bucket.
IAM Role policy for Cognito_IdentityPoolUnauth_Role
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SID_NUMBER",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::OUR_BUCKET_NAME/*"
]
}
]
}
S3 BUCKET POLICY
{
"Version": "2012-10-17",
"Id": "http referer policy example",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::IAM_NUMBER:role/Cognito_IdentityPoolUnauth_Role"
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::OUR_BUCKET_NAME/*"
}
]
}
When you attempt to access the files directly from the browser, no credentials are sent with the request. It has the same effect as trying to hit S3 directly without having any code to get credentials from Cognito. In order to use the Cognito credentials, you need to make the request using the javascript SDK. Below is a example request using the javascript SDK taken from this page
var s3 = new AWS.S3();
var params = {Bucket: 'myBucket', Key: 'myKey'};
s3.getSignedUrl('getObject', params, function (err, url) {
console.log("The URL is", url);
});
If you call this after getting Cognito credentials, it will use Cognito credentials to create a signed url to access the key myKey in bucket myBucket. You can take an approach similar to this to listObjects in your bucket and then generate signed urls (signed with Cognito credentials) that your end users can click on to view the contents of each key in your bucket.