AWS Amplify React GET request error - missing authentication token - amazon-web-services

I am using AWS Amplify in my react app to call my API hosted in API Gateway with AWS_IAM authorization. When I set authorization to NONE, everything works fine in my react app.
However, when I set authorization to AWS_IAM and execute my API using API.get() from Amplify like the code below:
const notes = await API.get('notes', '/notes', init);
I get an error message like:
{
"message": "Missing Authentication Token",
"err": "missing auth"
}
So I tried to use aws-api-gateway-cli-test to test my API gateway. Through the script, I was able to get a valid credential, get authenticated and correct response. I have also tried the POSTMAN with my admin credentials and it worked as well.
After doing some research, I saw people referring this to CORS issue. I have double checked my API gateway settings and confirmed that I have enabled CORS. The same issue persists.
Through the debugging feature of aws-amplify, I was able to see the signing process in my Chrome inspector. The signing process was performed properly with some accessKey and secretKey. I pulled those keys out of the inspector into my POSTMAN and tried to get.
These credentials are not valid and I received the following error message:
{
"message": "The security token included in the request is invalid.",
"err:": "default"
}
Update: I forgot to copy session token into POSTMAN. Now with all credentials generated by my app I am able to get correct result from my API in POSTMAN. Just not in my app.
At this point, it is pretty obvious to me that it is an auth problem. However, I have been using aws-amplify for sign-in and get process. Pretty sure the signing process is done by amplifying behind the scenes.
I have spent 3 days on this problem and basically tried everything... Any ideas on why it doesn't work?
P.S. A lot of people have suggested that typos in the invoke URL could lead to this issue. I have double checked and there is no typo. Below is my amplify configure code:
Amplify.configure({
Auth: {
mandatorySignIn: true,
region: config.cognito.REGION,
userPoolId: config.cognito.USER_POOL_ID,
identityPoolId: config.cognito.IDENTITY_POOL_ID,
userPoolWebClientId: config.cognito.APP_CLIENT_ID
},
Storage: {
region: config.s3.REGION,
bucket: config.s3.BUCKET,
identityPoolId: config.cognito.IDENTITY_POOL_ID
},
API: {
endpoints: [
{
name: "notes",
endpoint: config.apiGateway.URL,
region: config.apiGateway.REGION
}
]
}
});

Just resolved my problem - I have had Authorization settings for OPTIONS method to be AWS_IAM under Resources in API Gateway. However when my browser send a request it will send one to OPTIONS first to check for certain headers without the credentials in this request.
Since I set OPTIONS with IAM authorization, the OPTIONS method then checked against IAM with this request without my credentials. This is why I received "Missing Authentication Token".

The problem was this in my case:
import { Auth } from 'aws-amplify';
import { API } from 'aws-amplify';
I know, I now it's banal. This why I should't code when I am exausted.

Related

How To Invoke Cloud Functions With Workspace Users?

I am trying to invoke GCP functions through my angular app hosted on App Engine. I cannot seem to find any straightforward answer given my users are not signed up through GCP but through Google Workspace. So in short the app I am building is only for internal users. As of right now I am able to log in fine using Google Authentication, the problem is that after I log in, the cloud function is rejecting my request. I have included all the steps that I've taken along with the error I am receiving from the cloud function.
So heres what I've done so far.
I implemented the login button inside of my Angular app using
angularx-social-login.
I then obtained an OAuth 2.0 Client ID from
the GCP project in which the functions are hosted (as this is the
same project anyway).
After this I registered the OAuth consent screen and set it to internal as I don't
want anyone but my internal workspace users to be able to access this
I then went to users identity platform and registered the same OAuth
2.0 client ID that I spoke of in step 2.
I then set up the GCP function to allow allAuthenticatedUsers (I've tried many other
roles but I would accept if I could just get allAuthenticatedUsers to work for now)
Finally back in my angular app I passed into the function call
headers the idToken that I get each time a user logs in using the
Google Login Popup
My code looks like:
DashboardComponent.ts
import {SocialAuthService} from 'angularx-social-login';
...
this.authService.authState.subscribe((user) => {
this.myService.callFunction(user.idToken).subscribe((userRes) => {
...
}
...
FirebaseFunctionService.ts
callFunction(authToken): Observable<any> {
const headers= new HttpHeaders()
.set('content-type', 'application/json')
.set('Access-Control-Allow-Origin', '*')
.set('Authorization', `Bearer ${authToken}`);
return this.http.get('https://my-cloud-function-url/my-function', { headers: headers
});
}
And the response that I get when I call this function is:
error: ProgressEvent {isTrusted: true, lengthComputable: false,
loaded: 0, total: 0, type: 'error', …} headers: HttpHeaders
{normalizedNames: Map(0), lazyUpdate: null, headers: Map(0)} message:
"Http failure response for
https://myFunction.cloudfunctions.net/myFunction: 0 Unknown Error"
name: "HttpErrorResponse" ok: false status: 0 statusText: "Unknown
Error" url: "https://myFunction.cloudfunctions.net/myFunction"
[[Prototype]]: HttpResponseBase
Does anyone know why this might be happening? Any help would be greatly appreciated as I am at my wits end. Thanks in advance :)
Status code 0 in the error message indicates a CORS failure. You can go through this GitHub issue comment, where the stackoverflow thread points to a number of reasons for this error.
Also you need to write this line of code in your initialisations :
const cors = require('cors')({origin: true}) and check out Google’s documentation on how to handle CORS requests. Also you have to provide proper permissions - one of the important ones being giving Cloud Functions invoker role to your cloud functions.
Joe, (author of our question) agreed that it was a CORS error but he solved it by giving allUsers permission (making the function public) and verifying the user in the function itself and the CORS disappeared.
Now the reason behind this :
I think there was some issue with Joe’s authentication mechanism and hence functions were not authenticated. HTTP functions require authentication by default. And as it did not have it, as per documentation the workaround was to make Joe’s function public by setting the --allow-unauthenticated flag, or use the Console to grant the Cloud Functions Invoker role to allUsers. Then handle CORS and authentication in the function code (which he was doing as he mentioned).
So when Joe made the function public by granting allUsers and CORS was handled in code, it started working and the CORS errors disappeared.

AWS Elemental MediaConvert Client SDK not working

Since I have fallen into the AWS trap and not ignoring the info message on the Elastic Transcoder Page saying that, we should start using Elemental MediaConverter instead, pretty much nothing is working as expected.
I have set up the Elemental MediaConvert following these steps. I have to admit that setting up everything in the console was pretty easy and I was soon able to transcode my videos which are stored on my S3 bucket.
Unfortunately, the time had to come when I was forced to do the transcoding from my web application, using the #aws-sdk/client-mediaconvert. Apart from not finding any docs on how to use the SDK, I cannot even successfully connect to the service, since apparently MediaConvert does not support CORS.
So my question is, did anyone use MediaConvert with the SDK successfully? And if yes, could you please tell me what to do?
Here is my configuration so far:
try {
const client = new MediaConvertClient({
region: params.region,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: params.region }),
identityPoolId: params.cognitoPoolId,
}),
});
const data = new CreateJobCommand({
JobTemplate: "Test Template",
Role: "Cognito_TestTranscodeUnauth_Role",
Settings: {
Inputs: [
{
FileInput: "s3://some-bucket/files/video.mp4",
},
],
},
});
return await client.send(data);
} catch (error) {}
I have used the template successfully within the console
The cognito role has all the rights necessary for operating MediaConvert and the S3
The input does exist and works in the console
If I just run the script I get a CORS not allowed error. However, if I disable CORS in my browser, I just get an Access denied error without further explanation. It's really driving me mad!
Any help appreciated!
So, after almost two entire days of trial and error plus digging into the source code, I finally found a solution! To make it short: unauthenticated access and MediaConvert will not work!
The entire problem is Cognito which does not allow access to MediaConvert operations on unauthenticated access. Here is the access list.
My solution
Since I am using Auth0 for my user authentication I was simply following this guide and basically all my problems were gone! To attach the token I was using
// React state variable
[token, setToken] = useState<string>();
// Using the Auth0 SDK to set the
const { getIdTokenClaims } = useAuth0();
getIdTokenClaims().then(j => setToken(j.__raw));
// use this in eg. useEffect hook
fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: region }),
identityPoolId: identyPoolId,
logins: {
"<your-auth0-domnain>": token,
},
});
It seems that if we have an authenticated user,
we have no CORS errors
we can attach any role to the federated Identity pool we want
I guess there are other solutions asa well and I'm convinced that if you use the built in user service from the AWS for your app authentication, you probably will have less problems here.
Anyway, hope this helps someone!
P.S: the example from the original question does work without problems!

How to convert Amazon MWS credentials to SP-API creds

Here are the seemingly clear instructions from Amazon.
Simply send the following: sellingPartnerId, developerId, and mwsAuthToken
I do this with httparty like so:
query = {
sellingPartnerId: "A3Kxxxxx",
developerId: "753xxxx",
mwsAuthToken: "amzn.mws.8abxxxxx-xxxx-xxxx-xxxx-xxxxxx",
}
and then
send = HTTParty.get("https://sellingpartnerapi-na.amazon.com/authorization/v1/authorizationCode",
query: query
)
This returns the following error:
{"errors"=>
[{"message"=>"Access to requested resource is denied.",
"code"=>"MissingAuthenticationToken"}]}
I've adjusted the call everyway I've seen. I've read the following articles:
This
This
Paged through the 695 issues on github for this API and still no luck.. I've adjusted my query to this with no luck either:
query = {
grant_type: "client_credentials",
sellingPartnerId: "A3K98Oxxxxxx",
developerId: "753xxxxxxxx",
mwsAuthToken: "amzn.mws.8abxxxxxxx-xxxx-xxxx-xxxx-xxxxxxx",
client_id: "amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxx",
client_secret: "a473e76XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
scope: "sellingpartnerapi::migration"
}
Nothing I've tried has worked.. Any suggestions? Has anyone actually migrated their MWS to SP-API credential successfully?
Unfortunately the specific Amazon docs that you link to don't tell the whole story. There are a few other requirements you'll need in order to get the authorizationCode response that you're looking for:
Amazon OAuth Token
You'll need an access token from Amazon's OAuth API (an entirely different API). You can use the grantless workflow for this, since in your case the user hasn't actually authorized the SP-API yet:
POST https://api.amazon.com/auth/o2/token
body: {
grant_type: 'client_credentials',
scope: 'sellingpartnerapi::migration',
client_id: 'amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxx',
client_secret: 'a473e76XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
}
This will return an access_token that you'll need for your actual migration request to https://sellingpartnerapi-na.amazon.com/authorization/v1/authorizationCode. The response will look something like:
{
"access_token": "Atc|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"scope": "sellingpartnerapi::migration",
"token_type": "bearer",
"expires_in": 3600
}
Important: Take the access_token value from that response and add it as an x-amz-access-token header to your /authorization/v1/authorizationCode request.
Sign Your Request
This is the actual reason behind the error you're receiving. An unsigned request will not include the "authorization token" that you're being prompted for.
You'll need to sign your request using Amazon's SigV4 signing mechanism. It looks like you're using Ruby (HTTParty), so you can use the aws-sdk's Aws::Sigv4::Signer for this. You'll need to have setup IAM credentials as documented in the generic developer guide, and those credentials being provided to your Aws::Sigv4::Signer somehow (hardcoding, env vars, Aws::SharedCredentials, etc.)
Request signing will result in a few proprietary headers being added to your request. Once this is done, you should have all that you need to make the request successfully.

AWS: getting limited IAM credentials for DynamoDB without JS SDK

What I want to achieve:
My case described in this article but I do not want to use SDK on my webpage - I want to keep my app simple and fast, but as it turned out - it is hard to find an example that does not use SDK. I stumped with http call from which I need to get limited IAM credentials for DynamoDB. In my investigation of the documentation, I found method GetCredentialsForIdentity, description:
Returns credentials for the provided identity ID. Any provided logins
will be validated against supported login providers. If the token is
for cognito-identity.amazonaws.com, it will be passed through to AWS
Security Token Service with the appropriate role for the token.
But on this page not mentioned url for that endpoint. I tried
https://cognito-idp.us-east-1.amazonaws.com
https://cognito-identity.us-east-1.amazonaws.com
https://<mydomain>.auth.us-east-1.amazoncognito.com
I getting 400 error now, maybe because of incorrect endpoint url. My current code:
fetch('https://cognito-identity.us-east-1.amazonaws.com', {
'method': 'POST',
'headers': {
'Content-Type': 'application/x-amz-json-1.1',
'X-Amz-Target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
},
'body': '{"IdentityId": "us-east-1:<GUID of the user>"}'
});
1) What is the http endpoint?
2) Am I digging in the right direction?
Correct endpoint is
https://cognito-identity.us-east-1.amazonaws.com
according to this page.
Also, I found that I need to provide not a sub from id_token but firstly GetId of the user.

AWS cognito returning - 'Invalid Login Token. Not a Cognito Token'

I am able to successfully retrieve an identity token for my Custom Authentication Provider (Developer Authentication). This is implemented using the Cognito devauth demo servlet.
This token is returned to a browser running AWS JS SDK. When I make a call to getCredentialsForIdentiy, I receive the 'Invalid Login Token' error.
POST https://cognito-identity.us-west-2.amazonaws.com/ 400 (Bad Request)
app.js:150 Error: Invalid login token. Not a Cognito token.
at constructor.a (aws-sdk-2.58.0.min.js:41)
at constructor.callListeners (aws-sdk-2.58.0.min.js:41)
at constructor.emit (aws-sdk-2.58.0.min.js:41)
at constructor.emitEvent (aws-sdk-2.58.0.min.js:41)
at constructor.e (aws-sdk-2.58.0.min.js:41)
at i.runTo (aws-sdk-2.58.0.min.js:43)
at aws-sdk-2.58.0.min.js:43
at constructor.<anonymous> (aws-sdk-2.58.0.min.js:41)
at constructor.<anonymous> (aws-sdk-2.58.0.min.js:41)
at constructor.callListeners (aws-sdk-2.58.0.min.js:41) "NotAuthorizedException: Invalid login token. Not a Cognito token.
at constructor.a (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:615)
at constructor.callListeners (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:30513)
at constructor.emit (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:30224)
at constructor.emitEvent (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:16590)
at constructor.e (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:12285)
at i.runTo (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:43:7277)
at https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:43:7482
at constructor.<anonymous> (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:12495)
at constructor.<anonymous> (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:16645)
at constructor.callListeners (https://sdk.amazonaws.com/js/aws-sdk-2.58.0.min.js:41:30619)"
I am passing to getCredentialsForIdentity the following params.
identityId: <returned from servlet in region:guid format>
customRoleArn: <that maps to authenticated role>
Logins: <cognito-identity.amazonaws.com = token returned by congito>
I notice that new congnito identity was created by using id browser. So, the token interaction with the servlet seems correct. But the token is being rejected.
What am I missing here? How can I troubleshoot it further?
Edit:
Browser client: javascript sdk, version 2.58.
Essentially writing a JS client to this demo . The modifications is only to get token as part of login call itself. The generated token corresponds to a role that only has IOT client access. (I wonder if that policy needs to expand). Eventually the users will be validated against an internal ID store instead of this demo.
Edit 2:
Instead of calling getCredentialsForIdentiy against Cognito Service, I invoked assumeRoleWithWebIdentity against STS and that worked. The use of Basic Flow instead of Enhanced flow here. . Really not sure why enhanced flow did not work but will take the basic flow approach for now.
So, the AWS documentation here is either inaccurate or documents an inefficient scenario.
Using JavaScript Cognito API, my Developer Provider returned token could not be used with GetCredentialsForIdentity against Cognito API. (this is what prompted me to ask this question).
However, I can use the same token against STS API and invoke AssumeRoleWithWebIdentity which essentially returns the same response parameters as GetCredentialsForIdentity above. This is the approach I took.
Basic Auth Flow documentation in the first link seems inaccurate. Because it lists 'get credentials' and 'assume role' as required steps but only one is actually needed. Also, from a browser, this is an API call out to an Amazon service. So using Enhanced Flow instead of Basic does not seem like an advantage if it worked.
I had this same problem, using V3 of the Javascript SDK, and struggled for a very long time! The currently accepted answer worked for me - using the basic authflow instead of enhanced.
I am hoping to supplement the above with some information that other present-day readers using V3 of the SDK may find useful:
On the server, use GetOpenIdTokenForDeveloperIdentityCommand from #aws-sdk/client-cognito-identity to generate the token and send it to the client.
On the client, I was unable to successfully generate credentials with any of the following:
fromCognitoIdentity, from #aws-sdk/credential-providers
GetCredentialsForIdentityCommand, from #aws-sdk/client-cognito-identity
GetCredentialsForIdentityPoolCommand, from #aws-sdk/client-cognito-identity
The following did work, using #aws-sdk/client-sts:
const { STSClient, AssumeRoleWithWebIdentityCommand } = require("#aws-sdk/client-sts")
const client = new STSClient({region: cognitoRegion})
const assumeRoleCommand = new AssumeRoleWithWebIdentityCommand({
WebIdentityToken: openIdToken, // the token your server sends to the client
RoleArn: authRoleARN, // the ARN of the role you set up on AWS Cognito / IAM
RoleSessionName: userId // See note below
})
const credentialsResponse = await client.send(assumeRoleCommand)
// the client apparently expects slightly different property names than the received credentials object
const credentials = {
accessKeyId: credentialsResponse.Credentials.AccessKeyId,
expiration: credentialsResponse.Credentials.Expiration,
secretAccessKey: credentialsResponse.Credentials.SecretAccessKey,
sessionToken: credentialsResponse.Credentials.SessionToken
}
// Example using SES, you may be using different services
const sesClient = new awsSes.SESClient({
apiVersion: "2010-12-01", // yours may be different
region: "us-east-1", // yours may be different
credentials: credentials
})
Note: The AssumeRoleWithWebIdentityCommand docs discuss what you should use for the RoleSessionName.