Authorization code grant in lambda AWS with Serverless Framework - amazon-web-services

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

Related

Get new refresh token in oauth2.0 authorization code grant flow

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)
})
}

Get Cognito user pool identity in Lambda function

I have a Lambda function handling POST requests triggered by the API Gateway. The latter is set up to authorize via a Cognito user pool authorizer. Authorization works - if I pass a user's ID token, the request is processed, if I don't I get a 401.
However, I can't get the authorized user's identity in the Lambda function. All documentation makes me believe that it should be in the context, but it isn't. I can't map it in there, either. What's more, there doesn't seem to be a way to query the user pool for a user given their ID token, either.
Do I need an identity pool to accomplish this? If so, how does that work? And why wouldn't the API gateway automatically pass on the user's identity?
It depends on if you have Use Lambda Proxy Integration selected in the Integration Request for the lambda. If you have it set then all the token's claims will be passed through on event.requestContext.authorizer.claims.
If you are not using Lambda Proxy Integration then you need to use a Body Mapping Template in the Integration Request for the lambda. An example template with the application/json Content-Type is:
"context" : {
"sub" : "$context.authorizer.claims.sub",
"username" : "$context.authorizer.claims['cognito:username']",
"email" : "$context.authorizer.claims.email",
"userId" : "$context.authorizer.claims['custom:userId']"
}
This is expecting that there is a custom attribute called userId in the User Pool of course, and they are readable by the client.
You cannot use the id token against the aws cognito-idp APIs, you need to use the access token. You can however use AdminGetUser call with the username, if your lambda is authorized.
Use the event.requestContext.authorizer.claims.sub to get user's Cognito identity sub, which is basically their ID. This assumes you're using Proxy Integration with API Gateway and Lambda.
Here's a simple example using Node; should be similar across other SDKs.
exports.handler = async (event, context, callback) => {
let cognitoIdentity = event.requestContext.authorizer.claims.sub
// do something with `cognitoIdentity` here
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify("some data for user"),
};
return response;
};

How to retrieve idtoken from AWS Cognito who logged via Google/Fedarated Login

I've set up an application where users can register to my site/web application. Where
A.) A User can either join the site via registering their email/password combination and these users will be registered inside a Cognito User Pool.
B.) A User can join the site via logging in with Google/Facebook.
Bullet point A works as as expected. The user will submit their username and password, they will be able to login and they will receive the ID Token, AccessToken, and other information necessary as a response(I'm using aws amplify where it supeseded cognito javascript libraries). Now, when these users access a protected resource on API Gateway that has a cognito_authorizer enabled they will simply pass in
"Bearer " And they will be able to access
Where the is ID Token.
Now, for case B.
I am now able to login via social provider. I was able to configure all the necessary configurations in both google developer console, and registered google as federated entities.
Now, Thanks to aws amplify, I can perform a federated sign in by passing in the id token, and expires_at value that I have received from the google login.
However the only values I am able to received as a response from cognito is the CognitoIdentityCredentials
(https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityCredentials.html)
There is no access token, refresh token, and other necessary information.
Question is. Is it possible for federated sign in to retrieve an access token, and idtoken that is generated by cognito, and use that as token to be passed in as a header whenever I perform a request where a resource has a cognito_authorizer for users who joined my site via social login? Or am Missing certain steps to perform federated login that will return idtoken,and access token which is generated by cognito?
Here's the sample code I'm using
const profile = res.getBasicProfile();
const { id_token, expires_at } = res.getAuthResponse();
const user = {
email: profile.getEmail(),
name: profile.getName()
};
console.log(id_token);
Auth.federatedSignIn(
// Initiate federated sign-in with Google identity provider
'google',
{
// the JWT token
token: id_token,
// the expiration time
expires_at
},
// a user object
user
).then((a) => {
// ...location.reload();
console.log(a);
console.log(Auth.currentUserPoolUser());
});
And this is only the value I receive.
const getIdToken = async() => {return (await Auth.currentSession()).getIdToken().getJwtToken()}

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.

Using access token that was obtained from AWS cognito to protect Web API calls - Beginner

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