I set up an authorization code grant flow for Google using Amazon Cognito. I'm able to get authorization code by calling /login endpoint and exchange it for access_token, refresh_token and id_token using the /token endpoint so I assume that it's set up more or less properly.
Unfortunately, when I try to exchange a refresh_token for new tokens using /token endpoint as well, I receive only access_token and id_token without new refresh_token. I've been trying to understand why it happens but Amazon's documentation only briefly mentions that refresh_token is returned only for authorization code. What's more interesting, auth0 documentation says that the /token endpoint should behave in a very different way - it shouldn't return new tokens directly but a new authorization code instead.
And now I'm pretty confused about what happens there. Is it Amazon who changed the flow of authorizing a user using authorization code? Or, more likely, I don't understand it and did something wrong?
refresh_token is generated at once time code authorization, we can reuse it to generate new access_token and id_token. On my approach, I am calling initiateAuth method to generate new access_token and id_token :
refreshToken() {
let params = {
AuthFlow: "REFRESH_TOKEN_AUTH",
ClientId: this.clientId,
AuthParameters: {
"REFRESH_TOKEN": [refresh_token_property],
"DEVICE_KEY": null
}
};
return this.cognitoIdp.initiateAuth(params).promise().then(data => {
console.log(data.AuthenticationResult);
}).catch(e => {
console.log(e)
})
}
Related
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.
I'm developing a lambda service with Serverless Framework that is responsible for logging into Cognito.
const aws_cognito = require('amazon-cognito-identity-js');
const authDetails = new aws_cognito.AuthenticationDetails({
Username: usuario,
Password: password
});
const poolData = {
UserPoolId: XXXXXXXX,
ClientId: XXXXXXX
};
const userPool = new aws_cognito.CognitoUserPool(poolData);
const userData = {
Username: usuario,
Pool: userPool
};
const cognitoUser = new aws_cognito.CognitoUser(userData);
cognitoUser.authenticateUser(authDetails, {
onSuccess: () => {
console.log('OK');
},
onFailure: (err) => {
console.log(err);
}
});
For business reasons I need to simulate the UI that Cognito generates. The system should support OAUTH flows: "Authorization code grant" and "Implicit grant".
"Implicit grant" works without problems, but I can not obtain the authorization code for "Authorization code grant".
Is there any way to obtain the authorization code with the AWS SDK?
Thanks!
I understand that you are able to implement the "Implicit Flow" without using the Hosted UI but you want to know how to implement "Authorization Grant Flow".
You can use any HTTP client within your Web application to send HTTP
requests to Cognito Auth Endpoints to go through the Code grant flow.
These are REST API endpoints and also no SDK is required to perform
the operation.
Please see the below steps to understand the flow of the process using the API calls:
1) Make a GET request to AUTHORIZATION endpoint to receive the XSRF tokens [1].
You will need to pass the required parameters while making this request. The required parameters are response_type (code or token), client_id and redirect_uri. As per your use-case, since you are using "Authorization Grant Flow", you need the value of response_type to be set to "code".
Once you make this request, you will receive an XSRF token in the response as a Cookie. This XSRF token will be needed in the next step.
2) Make a POST request to LOGIN endpoint to receive tokens [2].
You need to pass the same required parameters mentioned while making AUTHORIZATION request.
Along with the required parameters, you can pass POST body parameters: CSRF token, username, and password.
Once we make this request, you will be able to receive the Tokens in the response. It also provides a Cookie in the response which you can use later to make a request for refresh tokens.
3) Make a POST request to the TOKEN endpoint to receive refresh tokens[3].
We need to pass the required parameters while making the request. The required request parameters are grant_type and client_id.
Once you make a successful request, you will receive a new set of Tokens in the response.
============
References:
[1] Authorization Endpoint: http://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
[2] Login Endpoint: http://docs.aws.amazon.com/cognito/latest/developerguide/login-endpoint.html
[3] Token Endpoint: http://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html
I'm using Amazon Cognito for authorization of my app.
I'm using the authorization code flow. I can successfully get my token on /oauth2/authorize?...
But I can't seem to successfully get access_token, id_token and refresh_token using the POST to /oauth2/token with the Content type header: application/x-www-form-urlencoded
and body:
{"key":"grant_type","value":"authorization_code"},
{"key":"client_id","value":"xyz"},
{"key":"redirect_uri","value":"redirect-url.com"},
{"key":"code","value":"code_from_previous_request"}
When I make this call I get the following error json:
{"error":"invalid_request"}
Client id is correct and client app has no secret.
Anyone has any idea what I'm doing wrong?
By taking a closer look a #MikePatrick's request I figured it out. I was sending a wrong parameter
redirect_url
instead of
redirect_uri
...
Note to self: Half of software bugs are caused by typos
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.
Finally with the greatest difficulty, I was able to make the user login work. My code is given below. I also got the Access Token printed to the console. So it all works fine.
Now, I need to access my AWS API Gateway function which is called saveHospitalInformation (Also which is accessed from https://awsxxxxxxxx/save-Hospital-Information ). How can I send my Access Token that I obtained from the above step to the AWS API Gateway function ? Is it in the header ? Can someone show me a code example?
I know that the Access Token is only valid for 1 hour. So, incase if it's expired what is the error message that is sent to the client ?
I am new to AWS and Access Tokens so can someone guide me here.
The code that i used for user Sign-in is given below:
// Cognito User Pool Id
AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1: XxxxxxxxxxxXxxxxxxxxxx'
});
var authenticationData = {
Username : 'username111',
Password : 'password123'
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var poolData = {
UserPoolId : 'us-east-1_XXXXXXXXX',
ClientId : 'XXXXXXXXXXXXXXXXXXXXXX'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var userData = {
Username : 'username111',
Pool : userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('22222222 1' );
console.log('access token + ' + result.getAccessToken().getJwtToken());
},
onFailure: function(err) {
// alert(err);
console.log('ERRR IS '+ err );
},
});
Apologies for the lack of examples for your specific problems. There are many ways to integrate these services, which is why you may not be able to find examples for your specific use case.
Let me try to answer your question in parts:
How can I send my Access Token that I obtained from the above step to
the AWS API Gateway function ? Is it in the header ? Can someone show
me a code example?
You currently have 2 options:
Use Cognito Federated Identity to generate AWS credentials. See the Cognito documentation for creating a credentials provider and API Gateway documentation for integrating that credentials provider with a generated Javascript SDK.
Use an API Gateway custom authorizer to validate the access token yourself. We have example authorizers that will validate a JWT generated by Cognito.
In option 1, the token is never sent to API Gateway, only to Cognito Identity. The SDKs should manage the lifecycle of your tokens, fetching a new access token when the current one expires.
In option 2, you are responsible for passing the token to the API. Custom authorizers currently support using a header on the incoming request to pass the token, which you can define when configuring it.
I know that the Access Token is only valid for 1 hour. So, incase if
it's expired what is the error message that is sent to the client ?
This will depend on the option you choose from above.
In option 1, the SDK should handle this for you. If for some reason the session expires, you will receive an error indicating that the user needs to login again.
In option 2, currently the custom authorizers will only return a 403 if you return an error. We are looking to improve this experience, but I cannot commit to a timetable for those updates.
recently I had to deal with OAuth2 authentication but I don't know AWS. In case you're in the same situation, here is an example of HTTP request with a token :
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
If helpful, more documentation about that here