Amplify API REST with AWS_IAM: Request failed with status code 403 - amazon-web-services

I'm trying to execute API calls from ReactNative AWS Amplify to API Gateway endpoint using AWS_IAM authorization.
I do it by calling (all Amplify initialization params are set):
import { API, Auth } from "aws-amplify";
...
API.get("MyApiName", "/resource")
.then(resp => { ... })
.catch(e => console.log(JSON.stringify(e));
I have console printout like:
{
"message":"Request failed with status code 403",
"name":"Error",
"stack": "...",
"headers":{
"Accept":"application/json, text/plain, */*",
"User-Agent":"aws-amplify/3.8.23 react-native",
"x-amz-date":"20210908T172556Z",
"X-Amz-Security-Token":"IQoJb3...",
"Authorization":"AWS4-HMAC-SHA256 Credential=ASIA23GCUWEDETN632PS/20210908/us-east-1/execute-api/aws4_request, SignedHeaders=host;user-agent;x-amz-date;x-amz-security-token, Signature=2a06fb4d8eb672164bfd736790fb1658edef1240d12a38afb599a9e33020c3cd"
...
}
So, it looks like the request is Signed!
I use Cognito User Pool and appropriate Identity Pool. They both are set properly, becuase these settings work with successfull authorization access to S3 storage using AWS Amplify S3.
Authenticated role for Cognito Identity Pool has permission to for ExecuteApi to invoke the API resource method. Also, it has permission to invoce the Lambda that is linked to the API's resource method.
All looks fine, but I am still getting the 403 Forbidden error.
What's missing here?

Related

Why do AWS-SDK API requests keep returning Missing Authentication Token?

I'm making Node.js API requests using the AWS-SDK, running from my EC2. But they keep returning error 403:
{"message":"Missing Authentication Token"}
I'm trying to access Amazon SES endpoints. I have a user set in IAM with one policy added. Its policy name is AmazonSESFullAccess. This user's key and secret are stored in ~/.aws/credentials.
This should be enough to authenticate and authorise my request: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html
I also added them to my project's .env file, using the recommended AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.
Which should also successfully authenticate and authorise my calls: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
According to these, if I'm using the AWS-SDK to send my request, I shouldn't need to sign them in headers or query parameters: https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html
Here's the code:
test: async () => {
/**
* Call AWS SES API
* Returns information about the account
* https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetAccount.html
*/
return new Promise(resolve => {
axios
.get("https://email.us-east-1.amazonaws.com/v2/email/account")
.then(res => {
console.log(res);
resolve(res);
})
.catch(error => {
console.error(error)
});
});
}
So why can't I access any resources? What can I do to fix it?
You are not using the NodeJS AWS SDK. You are just sending a "normal" HTTP request using axios. The request you are sending is not at all aware of any AWS authentication credentials.
That's why you are making unauthenticated requests and getting the corresponding error "Missing Authentication Token".
You should use the AWS SDK to "talk" to the AWS API:
var sesv2 = new AWS.SESV2();
var params = {};
sesv2.getAccount(params, function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
});
See: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SESV2.html#getAccount-property
The AWS SDK will read the credentials and take care of authenticating requests.

AWS Amplify 'currentUserCredentials()' returns unexpected authenticated value, bug?

When calling Auth.currentUserCredentials() after Auth.signIn() I get valid valid credentials, but for an unauthenticated user, why?
Code:
async signIn({ dispatch, state }, { email, password }) {
try {
const user = await Auth.signIn(email, password);
console.log("User state after login")
const userCredentialsAfterLogin = await Auth.currentUserCredentials();
console.log(userCredentialsAfterLogin)
} catch (error) {
console.log("error")
console.log(error)
return
}
await dispatch('fetchUser')
},
Expected behaviour:
After signing in with a valid user, Auth.currentUserCredentials() should return an authenticated set of Credentials.
Actual behaviour:
Auth.currentUserCredentials() returns an unauthenticated user with the authenticated property set to false and a 400 error, "ResourceNotFoundException: IdentityPool 'eu-west-1:62dab5ed-5a84-4064-a7a2-87d1d0df511b'
System:
authenticationFlowType: "USER_SRP_AUTH"
Versions: "aws-amplify": "^3.3.14", "aws-amplify-vue": "^2.1.4", amplify version 4.42.0
config
{
"authSelections": "userPoolOnly",
"resourceName": "testapp89e81d50",
"serviceType": "imported",
"region": "eu-west-1"
}
I understand where you're coming from, and honestly I can't really show you clear documentation that exactly states why this won't work. AWS documentation on Cognito and Amplify is difficult to piece together, both because the Amplify framework still uses an old library under the hood ('amazon-cognito-identity-js') and Cognito is the name for both a connect-login-with-IAM and signup/signin-as-a-service offering. Cognito is super powerful and rock solid in terms of security if done right, but the setup is a bit of a pain.
There's a bit of documentation, e.g. the API docs of Amplify Auth here. You can see there that currentCredentials / currentUserCredentials gives you some object which contains among other things an 'identitiId'. Credentials, in the Amplify Auth framework, refer to AWS IAM credentials that refer to an Cognito Identity.
You seem to be using Amplify to login to a Cognito User Pool, using email/password. A Cognito User Pool can be connected to a Cognito Identify pool, to 'exchange' a Cognito JWT token for some credentials that can be used to use AWS resources (IAM credentials). This is not needed to have a normal sign in / sign up flow working though.
So, the question is: what do you want?
Do you want to know about the currently logged in Cognito User Pool user, e.g. his email, JWT token and other fields that are stored in the JWT token? Use 'currentUserInfo' or 'currentUserSession'
Do you actually want to have some IAM credentials to invoke AWS resources? Make sure to create and connect your Cognito User Pool with a Cognito Identity Pool and configure your Identity Pool id in your frontend settings. If you've done that, you should be able to use 'currentCredentials'.

Accessing AWS AppSync without using API Keys in React Native

In my React Native App I am using API Keys with AWS AppSync and I want to move to using Cognito or IAM but with no user sign in.
My React Native app that just uses AWS Appsync to read to and write from DyanmoDB.
I initially set up the app to use API keys as it was easier to understand and I'm now attempting to transition to using AWS Cognito or IAM.
To do this in my AWS Console I changed the "Appsync->MyAppAPI->Settings->Default authorisation mode"/"API-level" from "API key" to "AWS Identity and Access Management (IAM)".
I then created an Identity Pool and allowed "Enable access to unauthenticated identities".
My aws-exports file is
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.
const awsmobile = {
"aws_appsync_graphqlEndpoint": "https://xxxxx.appsync-api.eu-west-x.amazonaws.com/graphql",
"aws_appsync_region": "eu-west-X",
"aws_appsync_authenticationType": "AWS_IAM",
"aws_appsync_apiKey": "xxx-xxxxxxxxxxxxxxxxxxxxxxxxxx",
};
export default awsmobile;
In my App.js file I have attempted to get the identityPoolId to be used and I have created this:
Amplify.configure({
url: config.aws_appsync_graphqlEndpoint,
region: config.aws_appsync_region,
auth: {
type: config.aws_appsync_authenticationType,
apiKey: config.aws_appsync_apiKey,
region: 'eu-west-x',
identityPoolId: 'eu-west-x:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
}
})
When I use the app to read from DynamoDB I get this error
[WARN] 04:51.835 API - ensure credentials error, No Cognito Federated Identity pool provided
I then went back to the Cognito Manage Identity Pool page and changed my identity pool to use Cognito as an authentication source and provided a User Pool ID and an App client id. But I still get the same error.
I am fundamentally missunderstanding something, could you offer any insight?

Cannot test Cognito authenticated API Gateway call in Postman (its an ADMIN_NO_SRP_AUTH pool)

I've managed to successfull login to the API gateway I've made via my iOS device and Cognito. The problem is I'd like to use postman to test the API calls then implement them on the phone. Currently, Postman cannot authenticate (despite AWS saying it can). No matter what I do I get a 401 error (visible in the screen-shots)
What I've tried
Downloaded the postman collection from AWS Api Gateway
Then imported it into postman, and switch the authentication to "AWS Signature"
And Here is a screen shot of the Postman Generated Header Info
If I understand correctly, you are trying to call an API Gateway endpoint that is behind the built-in Cognito Authoriser.
I think you've misunderstood how you call an Cognito Authorised API Gateway:
Authorise against Cognito to get an id_token
Call API Gateway with the Authorization header set to id_token
Renew id_token every hour
By enabling ADMIN_NO_SRP_AUTH you're allowing the first step (sign-in to Cognito) to be simplified so that you can more easily do it manually. (If you hadn't, then you would need to do SRP calculations).
One way to get the id_token is to use the aws cli (further ways are shown in the documentation):
aws cognito-idp admin-initiate-auth --user-pool-id='[USER_POOL_ID]' --client-id='[CLIENT_ID]' --auth-flow=ADMIN_NO_SRP_AUTH --auth-parameters="USERNAME=[USERNAME],PASSWORD=[PASSWORD]"
You can then use the result (AuthenticationResult.IdToken) as the Authorization header in Postman (no need for the AWS v4 signature- that is only for IAM authentication).
n.b. a much fuller explanation with images can be found here.
Here is what I finally did to fix postman auth issues
1) Turned off App Client Secret in the Cognito pool.
2) Ran aws --region us-east-1 cognito-idp admin-initiate-auth --cli-input-json file://gettoken.json
JSON file example
{
"UserPoolId": "us-east-1_**********",
"ClientId": "******************",
"AuthFlow": "ADMIN_NO_SRP_AUTH",
"AuthParameters": {
"USERNAME": "*********",
"PASSWORD": "***********"
}
}
3) Went to Postman > Authorization > Bearer Copied the idToken value into the token field and everything worked.
NOTE: For those wondering if not using a secret client key is safe. See this article.

InvalidClientTokenId when using IAM user

I'm having trouble implementing GetCallerIdentity with AWS within my application. When I try to generate temporary credentials, the server console returns the error InvalidClientTokenId: The security token included in the request is invalid.
I've been looking at the AWS documentation and I'm wondering if I've set up my IAM user incorrectly. The documentation says;
The temporary security credentials created by GetSessionToken can be used to make API calls to any AWS service with the following exceptions:
You cannot call any IAM APIs unless MFA authentication information is
included in the request.
You cannot call any STS API except AssumeRole or GetCallerIdentity.
Does this mean I can't use IAM accounts with the below example? I'm building in meteor js and I'm trying to implement a package called SlingShot. The documentation doesn't mention errors like this.
var sts = new AWS.STS();
sts.getSessionToken({
DurationSeconds: duration
}, function (error, result) {
console.log('error', error);
callback(error, result && result.Credentials);
});