How to call AWS API Gateway Endpoint with Cognito Id (+configuration)? - amazon-web-services

I want to call an AWS API Gateway Endpoint that is protected with AWS_IAM using the generated JavaScript API SDK.
I have a Cognito UserPool and a Cognito Identity Pool. Both properly synced via ClientId.
I use this code to Sign in and get the Cognito Identity
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:XXXXXXXXXXXXXXXXXXXXXXXX' // your identity pool id here
});
AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:XXXXXXXXXXXXXXXXXXXXXXXX' // your identity pool id here
});
var poolData = {
UserPoolId: 'us-east-1_XXXXXXXX',
ClientId: 'XXXXXXXXXXXXXXXXXXXXXXXX'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationData = {
Username: 'user',
Password: '12345678',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: 'user',
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:XXXXXXXXXXXXXXXXXXXX',
IdentityId: AWS.config.credentials.identityId,
Logins: {
'cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXX': result.idToken.jwtToken
}
});
AWS.config.credentials.get(function (err) {
// now I'm using authenticated credentials
if(err)
{
console.log('error in autheticatig AWS'+err);
}
else
{
console.log(AWS.config.credentials.identityId);
}
});
},
onFailure: function (err) {
alert(err);
}
});
All this succeeds and I have an authorized Cognito Identity now.
Now I try to call the API Gateway Endpoint to execute the Lambda Function it points to.
var apigClient = apigClientFactory.newClient({
accessKey: AWS.config.credentials.accessKeyId, //'ACCESS_KEY',
secretKey: AWS.config.credentials.secretAccessKey, //'SECRET_KEY',
sessionToken: AWS.config.credentials.sessionToken, // 'SESSION_TOKEN', //OPTIONAL: If you are using temporary credentials you must include the session token
region: 'us-east-1' // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1
});
var params = {
// This is where any modeled request parameters should be added.
// The key is the parameter name, as it is defined in the API in API Gateway.
};
var body = {
// This is where you define the body of the request,
query: '{person {firstName lastName}}'
};
var additionalParams = {
// If there are any unmodeled query parameters or headers that must be
// sent with the request, add them here.
headers: {},
queryParams: {}
};
apigClient.graphqlPost(params, body, additionalParams)
.then(function (result) {
// Add success callback code here.
console.log(result);
}).catch(function (result) {
// Add error callback code here.
console.log(result);
});
But unfortunately this fails. The OPTIONS request succeeds with 200 but the POST then fails with 403.
I am pretty sure that there is no CORS problem here.
I am pretty sure the problem has to do with IAM Roles and AWS Resource Configurations.
My question is basically, can you please provide me with all the necessary AWS Resource Configurations and IAM Roles that are necessary for this to work please?
Resources I have are
API Gateway - with deployed API Endpoints
Lambda Function - called by the Endpoint
Cognito User Pool - with App synced to the Identity Pool
Cognito Identity Pool - with Authorized and Unauthorized Role mapped to it.
IAM Roles - for the Lambda Function and the Authorized and Unauthorized Role of the Cognito Identity Pool.
But I don't know how these Resources need to be configured properly to get this to work.
Thank you

What access permissions does the role of the Cognito Identity have? Make sure it has access to perform execute-api:Invoke on your API.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:us-east-1:<account>:<rest-api>/*/POST/graphql"
]
}
]
}
You can get the exact resource ARN from the method settings page in the web console.

Even after following everything I was getting the same error. And the reason was I missed the "sessionToken" while initialising the apigClient.
var apigClient = apigClientFactory.newClient({
accessKey: AWS.config.credentials.accessKeyId, //'ACCESS_KEY',
secretKey: AWS.config.credentials.secretAccessKey, //'SECRET_KEY',
sessionToken: AWS.config.credentials.sessionToken, // 'SESSION_TOKEN', //OPTIONAL: If you are using temporary credentials you must include the session token
region: 'us-east-1' // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1 });
//OPTIONAL: If you are using temporary credentials you must include the session token -- is not really optional

Related

AWS getting credentials from Cognito Identity Pool using user pool JWT

I am trying to build a function in lambda that would get the user’s AWS credentials (in order to make an Authorization signature to request S3 resources.
event.headers.Authorization is a JWT from Cognito user pool sign in and is used in a lot of other functions and works properly.
Below in the code snippet I tried to use to get the credentials
EDIT: I can confirm that the JWT used in the Authorization header here is an ID token
console.log(event.headers.Authorization)
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'ap-northeast-2:443c5880-d302-4dcf-8cea-b1835723cdb4',
Logins: {
'cognito-idp.ap-northeast-2.amazonaws.com/ap-northeast-2_onyCNlZBF': event.headers.Authorization
}
});
console.log(AWS.config.credentials)
Below is the response , the access key and session token is missing
CognitoIdentityCredentials {
expired: true,
expireTime: null,
refreshCallbacks: [],
accessKeyId: undefined,
sessionToken: undefined,
params: {
IdentityPoolId: 'ap-northeast-2:443c5880-d302-4dcf-8cea-b1835723cdb4',
Logins: {
'cognito-idp.ap-northeast-2.amazonaws.com/ap-northeast-2_onyCNlZBF': 'eyJraWQiOiJJVlpiNkZTUUJudWlnZHRZMldrMkZuQTNXaHQ1dVNpTVhFdlVQaE0xSFdzPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI3MDdhNjQ0Ny04NzZiLTRlNDItYjc1OS0zNWIxZmI3YTQ2NzciLCJhdWQiOiIycjJjZ2V2dGkyOW9mZzg3bWJuaTZwazdwdSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJldmVudF9pZCI6ImUyNjM2ZDdkLTgzZTUtNDgwNC04ZmJjLTQwN2IyNTU3NmU3YyIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNTkxMDY3NzI1LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbVwvYXAtbm9ydGhlYXN0LTJfb255Q05sWkJGIiwiY29nbml0bzp1c2VybmFtZSI6ImFsZXgiLCJleHAiOjE1OTEwNzEzMjYsImlhdCI6MTU5MTA2NzcyNiwiZW1haWwiOiJhbGV4Lnd0aG9AZ21haWwuY29tIn0.7fPwMZEVgOIroiO32bOxAyWxaBkFQco772j9i8m3LNpMx2NxW0UzlE-8J4bp6T0np6HK0MaPgg9BY0qfKjTFYWuMzf6mA7ah6aW30U7yosOyzsuK1CWz8Ksa_-QneLtMcbFVxyAZ8jWqK-TQXhS0IctPK4zehuugvymfjzC11GPcZ9sWoS3X-u2jSebUSta1pce_EEgL3rsL3XUZIxnZZiAqYw-vmFnz64ATqYa13ggsSoGYsATU5JTmO_tTut3xsitp_s7m5jCkqouzj11XvuBZDITXiZPN1ZY62jQ6Mhk9Kin1558DNxhgb2lJTcwUSr6577bVwWJsimp22ca0gw'
}
},
data: null,
_identityId: null,
_clientConfig: {}
}

Cognito Identity Credentials are not authorized to perform action on Kinesis video resource

I am trying to allow access to a Kinesis video stream using Cognito Identity Pools, but get an AccessDeniedException when calling GetDataEndpoint.
IAM Role Policy Doc:
{
"Sid": "Stream",
"Effect": "Allow",
"Action": [
"kinesisvideoarchivedmedia:GetHLSStreamingSessionURL",
"kinesisvideo:GetDataEndpoint"
],
"Resource": "arn:aws:kinesisvideo:us-west-2:XXXXXXXXXXXX:stream/<stream-name>/<stream-id>"
}
I have tested the policy using the policy simulator, and it shows that the GetDataEndpoint action is allowed on the stream, but when testing it in the browser the access denied exception occurs:
AccessDeniedException:
User: arn:aws:sts::XXXXXXXXXXXX:assumed-role//CognitoIdentityCredentials
is not authorized to perform: kinesisvideo:GetDataEndpoint on resource:
<resource-name>
This is how I'm getting the temporary credentials on the site:
AWS.config.region = 'us-west-2';AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: <identity-pool>,
});
AWS.config.credentials.get(function (err, data) {
if (!err) {
id = AWS.config.credentials.identityId;
accessKey = AWS.config.credentials.accessKeyId;
secretKey = AWS.config.credentials.secretAccessKey;
token = AWS.config.credentials.sessionToken;
}
});
I've tried using wildcards for the Kinesis video actions and the resource, but still get the same errors. Any advice would be appreciated.
This will be due to the scope down policy that Cognito applies to unauthenticated users. It is further explained here:
https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html
As stated in the above documentation:
If you need access to something other than these services for your
unauthenticated users, you must use the basic authentication flow.
To easily solve this you should also pass the unauthenticated role RoleArn to CognitoIdentityCredentials.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: <identity-pool>,
RoleArn: <unauthorizedRoleArn>
});
This will ensure that, as specified here,
If a RoleArn is provided, then this provider gets credentials using the AWS.STS.assumeRoleWithWebIdentity() service operation, after first getting an Open ID token from AWS.CognitoIdentity.getOpenIdToken()
This essentially means that the credentials will be provided using the Basic (Classic) Flow
In addition to this you should also Allow Basic (Classic) Flow in your Identity Pool Authentication flow settings
I know this is old but I struggled with this for hours and couldn't get it to work even with RoleArn: <unauthorizedRoleArn> and following the suggestions in this issue.
In my case, my users are already authenticated via Amplify.Auth.signIn() but I needed to use AWS.KinesisVideo() which isn't included in the amplify sdk.
Ended up using the pre-generated AWSCrendentials post signIn. Might not be the best approach but it does the job.
const checkCognitoUserSession = async () => {
const getAwsCredentials = await Auth.currentCredentials();
const awsCredentials = await Auth.essentialCredentials(getAwsCredentials);
return awsCredentials;
};
const awsCredentials = await checkCognitoUserSession();
AWS.config.update({
credentials: new AWS.Credentials({
accessKeyId: awsCredentials.accessKeyId,
secretAccessKey: awsCredentials.secretAccessKey,
sessionToken: awsCredentials.sessionToken,
}),
});
new AWS.KinesisVideo({ apiVersion: '2017-09-30', region: config.Auth.region });

Cognito Identity with OpenId Provider and Conditional DynamoDb Policy

i use a custom open id provider and aws accept my tokens and i'm able to use coginto identity pools to limit the access to my dynamodb database. My problem is that when i use coginito identity with dynamodb:LeadingKeys -> ${cognito-identity.amazonaws.com:sub}' it always prepend the aws-region before my userId: Example: eu-central-1:1234567
In my database design i only need the userId (without the region) as the primary. Are there any possibilities to remove the region from sub. i already tried to change the Condition to my custom openid provider (like the documentation described) - but this always don't work
my Policy:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:BatchWriteItem
Resource:
- arn:aws:dynamodb:eu-central-1:123:table/table
Condition:
ForAllValues:StringEquals:
dynamodb:LeadingKeys: ${custom.open.id/openid:sub}
My Javascript code looks like this:
const Logins = {}
Logins['custom.open.id'] = accessToken; // my own openId access JWT Token
// Add the User's Id Token to the Cognito credentials login map.
const credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: identityPoolId,
Logins: Logins
});
return credentials.getPromise()
.then( error => {
if (error) {
return Promise.reject(error)
}
if (credentials.needsRefresh() === true) {
return credentials.refreshPromise()
}
return null;
})
.then(error => {
if (error) {
return Promise.reject(error)
}
console.log('Successfully logged!', credentials.data);
const dynamoDb = new AWS.DynamoDB.DocumentClient({
credentials: credentials,
region: region,
});
const params = {
TableName: "table",
KeyConditionExpression: "#userId = :userId",
ExpressionAttributeNames: {
"#userId": "userId"
},
ExpressionAttributeValues: {
":userId": currentUserId
}
}
return dynamoDb.query(params).promise();
})
.then(data => {
console.log("DynamoDb", data)
})
And the content of my JWT Access token is:
{
"sub": "123",
"nonce": "VRN3voQIhS6Nb6AzSsv907GxPnKc0szo",
"sid": "4fd7fd36-5a36-40e6-b381-b004a52be473",
"at_hash": "wF9BX32r6yQqvV5j0QGj8g",
"s_hash": "NzqHNkNF7FfCJa1tKudjTg",
"aud": "test_client",
"exp": 1556560701,
"iat": 1556557101,
"iss": "https://custom.open.id/openid"
}
The Sub in this IAM JSON is the Identity ID. An Identity ID is defined as a unique Identity for each user attempting to get credentials via a Cognito Identity Pool.
There is no way to change the pattern of an Identity ID, as the format is predefined by Cognito's architecture. For more details on Cognito Identity Pool's Roles and Policies, kindly refer to this official AWS Blog Post.

Firebase as Identity Provider with Cognito / AWS

I am having a hard time using Firebase as an Open ID Connect provider.
Can you please further describe the steps you have been through before and after to make this work?
For information, here is what I have done so far:
In AWS Console:
1 - Create an IAM Identity Provider ( OpenID Connect ) and used securetoken.google.com/<FIREBASE_PROJECT_ID> as an URL, <FIREBASE_PROJECT_ID>for Audience
2 - Checked the Thumbprint manually (it matches the one generated by AWS)
3 - Created a role with the permissions to access the desired services
4 - Created an Identity Pool in Cognito and selected my newly created role in the 'Authenticated role' Dropdown
5 - Selected my Identity Provider under the Authentication Providers > OpenID category (format is therefore): securetoken.google.com/<FIREBASE_PROJECT_ID>
In my code (I am using Vue.js) here are the logical steps I went through:
Import / setup AWS SDK
Invoke Firebase Auth service
Create a new CognitoIdentity
Use the getOpenIdTokenForDeveloperIdentity and push the tokenID received from Firebase
The issue is that I keep getting "Missing credentials in config" errors.
The code:
import axios from 'axios';
const AWS = require('aws-sdk');
AWS.config.region = 'eu-west-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_COGNITO_POOL_ID',
});
export default {
name: 'My Vue.js component name',
data() {
return {
email: '',
password: '',
msg: '',
};
},
methods: {
submit() {
axios
.post(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=MY_KEY',
{
email: this.email,
password: password,
returnSecureToken: true,
},
)
.then((res) => {
// stores tokens locally
localStorage.setItem('jwt', JSON.stringify(res.data));
const cognitoidentity = new AWS.CognitoIdentity();
const params = {
IdentityPoolId: 'MY_COGNITO_POOL_ID',
Logins: {
'securetoken.google.com/<PROJECT_ID>': res.data.idToken,
},
IdentityId: null,
TokenDuration: 3600,
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, (err, data) => {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
});
},
},
};
Here are the resources I have used so far while attempting to make this work:
http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
Using Firebase OpenID Connect provider as AWS IAM Identity Provider
https://github.com/aws/amazon-cognito-identity-js/blob/master/examples/babel-webpack/src/main.jsx
http://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-2-developer-authenticated-identities/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-3-roles-and-policies/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-4-enhanced-flow/
The final code if that can be any help for anyone:
import axios from 'axios';
const AWS = require('aws-sdk');
const aws4 = require('aws4');
export default {
name: 'VUE_CPNT_NAME',
data() {
return {
email: '',
password: '',
msg: '',
idToken: '',
};
},
methods: {
submit() {
// Firebase SignIn API
// Doc: https://firebase.google.com/docs/reference/rest/auth/
axios
.post(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[MY_KEY]',
{
email: this.email,
password: this.password,
returnSecureToken: true,
},
)
.then((res) => {
this.idToken = res.data.idToken;
localStorage.setItem('jwt', JSON.stringify(res.data));
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
Logins: {
'securetoken.google.com/<FIREBASE_PROJECT_ID>': res.data.idToken,
},
}, {
region: 'eu-west-1',
});
// AWS.config.crendentials.get() methods works as well
// or a call to cognitoidentity.getId() followed by a call to getCredentialsForIdentity()
// will achieve the same thing. Cool. But why!?
AWS.config.getCredentials((err) => {
if (err) {
console.log(err);
}
const request = {
host: 'API_GATEWAY_ENDPOINT.eu-west-1.amazonaws.com',
method: 'GET',
url: 'https://API_GATEWAY_ENDPOINT.eu-west-1.amazonaws.com/PATH',
path: '/API_ENDPOINT_PATH',
};
// Signing the requests to API Gateway when the Authorization is set AWS_IAM.
// Not required when Cognito User Pools are used
const signedRequest = aws4.sign(request,
{
secretAccessKey: AWS.config.credentials.secretAccessKey,
accessKeyId: AWS.config.credentials.accessKeyId,
sessionToken: AWS.config.credentials.sessionToken,
});
// removing the Host header to avoid errors in Chrome
delete signedRequest.headers.Host;
axios(signedRequest);
});
});
},
},
};
Try setting the login map i.e the firebase token in the CognitoIdentityCredentials object. See this doc.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_COGNITO_POOL_ID',
Logins: {
'securetoken.google.com/':
}
});
Try calling get method on the credentials object before initializing the Cognito client. You can also use getCredentials instead.
If the above steps do not work & they should, pass the credentials as an option while initializing the Cognito client. See this doc for options available while using the CognitoIdentity constructor.
const cognitoidentity = new AWS.CognitoIdentity({credentials: AWS.config.credentials});
If you still receive the error, try logging credentials object in the console after calling the get() method. Ideally, it should have temporary credentials (accessKey, secretKey & sessionToken)

How to get AWS credentials with a UserPools idToken?

Im currently using a USER-POOLS authorizer to get the first 3 tokens for my API:
idToken
refreshToken
accessToken
From here I would like to request credentials to be able to SigV4 request to my already set up API gateway, but first I need to get the requested credentials in order to do the SigV4.
In the docs I found this:
// Set the region where your identity pool exists (us-east-1, eu-west-1)
AWSCognito.config.region = 'us-east-1';
// Configure the credentials provider to use your identity pool
AWSCognito.config.credentials = new AWSCognito.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:009xxxx ...',
});
// Make the call to obtain credentials
AWSCognito.config.credentials.get(function(){
// Credentials will be available when this function is called.
var accessKeyId = AWSCognito.config.credentials.accessKeyId;
var secretAccessKey = AWSCognito.config.credentials.secretAccessKey;
var sessionToken = AWSCognito.config.credentials.sessionToken;
});
To my surprise, the callback is called but the values for the
- accessKeyId
- secretAccessKey
- sessionToken
are all null.
I was expecting some kind of method, where I send my first idToken, and based on that I get the credentials, but it looks like this is all figured out under the hood?, anyways it is not working for me.
After some research, I realised that there is an undocumented way of doing this.
You need to construct this object first:
let url = 'cognito-idp.' + 'identity pool region' + '.amazonaws.com/' + 'your user pool id';
let logins = {};
logins[url] = idTokenJwt; // <- the one obtained before
let params = {
IdentityPoolId: 'the federated identity pool id',
Logins: logins
};
let creds = new AWS.CognitoIdentityCredentials(params);
AWS.config.region = 'us-east-1';
AWS.config.credentials = creds;
creds.get(function (err: any) {
if (!err) {
console.log("returned without error"); // <-- this gets called!!!
// and the values are correctly set!
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
}
else{
console.log("returned with error"); // <-- might get called if something is missing, anyways self-descriptive.
console.log(err);
}
});
In my case I still had to configure the trust relationship between the role and the identity pool, here the example:
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "your federated identity pool id"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
*You can also replace "authenticated" with "unauthenticated", "graph.facebook.com", "google ...", depending your needs.