Get Cognito user pool identity in Lambda function - amazon-web-services

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

Related

How to pass token from openID connect provider to AppSync api

I have set up AppSync to use OIDC / OpenID Connect as authoriser and have gotten token on client successfully. I am not using cognito. I am now trying to figure out following:
I am using apollo sdk to interact with my AppSync GraphQL api, how do I pass this open id token from client to it? I assume it has to be some sort of header, but I am unable to find any docs on what AppSync expects it to be.
Once I pass the token, how do I access its claims in AppSync / AppSync resolver, after it has been verified and token data extracted?
Pass the Firebase token when you create the client:
const client = new AWSAppSyncClient({
url: ...,
region: ...,
auth: {
type: 'OPENID_CONNECT',
jwtToken: async() => token // Token from Firebase
}
})
The claims from the token are available in $context.identity . Some of these claims are standard claims (specified by OpenID Connect) and others are custom claims (unique to your project). The shape of $context.identity will be similar to one emitted had you used AWS Cognito instead of Firebase:
{
"sub" : "...", // standard claim - straight from the token
"issuer" : "...", // standard claim - straight from the token
"username" : "...",
"claims" : { ... }, // custom claims from the token
"sourceIp" : ["x.x.x.x"],
"defaultAuthStrategy" : "..."
}
As you can see above, sub and iss are two standard claims that are mapped directly from the token. All other claims from the token are surfaced as custom claims. They can be retrieved via $context.identity.claims.get()
You could then use these claims in a resolver and act on them in a standard fashion.

is the Authorization header passed to lambda function by Cognito Authorizer?

I'm planning to add some authorisation logic to my web app. Users are managed & authenticated by Cognito, and the API is powered by Lambda functions glued together with API Gateway.
Currently, Cognito just validates the user's OAuth token and allows/denies the request.
I'd like to further restrict what actions the user can take within my lambda functions by looking at the user's groups.
Looking at the OAuth token, claims about the groups are in the token body. My question is, does the Cognito Authorizer pass the value of the Authorization: Bearer foo header through to API Gateway and the Lambda handler?
The way I can do something like this:
const groups = getGroupsFromToken(event.headers.Authorization);
if (groups.includes('some group')) {
// let user do the thing
}
else {
callback({ statusCode: 401, body: 'You can\'t do the thing' });
}
It definitely sends through the token on a header for me, also it sends through requestContext.authorizer.jwt.claims which may be more useful to you.
The older api gateways I have always uppercase the header to "Authorization", irrespective of what case the actual header uses. The newer ones always lowercase it to "authorization".
I'd suggest trying:
const groups = getGroupsFromToken(event.headers.Authorization || event.headers.authorization);
I am using lambda proxy integration (what the new APIGW UI is calling lambda integration 2.0), from your callback it looks like you are using it too. If you are using the old lambda integration (1.0 in the new UI) then you need a mapping template.

Authorization code grant in lambda AWS with Serverless Framework

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

AWS ApiGateway Lambda Proxy access Authorizer

I´m using an Lambda Proxy and a Cognito User Pool Authorizer in my ApiGateway. In the Lambda function I can access the path etc. variables via the event object. In addition to that I want to access the claims of the authenticated user. In the documentation it is written, that I should use:
context.authorizer.claims.property
But I authorizer is null so I get
Cannot read property 'claims' of undefined
Anyone with an idea?
The accepted answer will work but it is not needed. When using Lambda Proxy Integration you can access the authorizer claims at:
event.requestContext.authorizer.claims
You can try to console.log(event); and see the information you get out of a Lambda Proxy Integration in CloudWatch Logs.
If you are referring to this part of the documentation, $context.authorizer.claims is part of the mapping template of the integration. It is not related to the context argument of the handler.
Using Lambda Proxy integration, you are using the passthrough mapping template. I̶t̶ ̶s̶e̶e̶m̶s̶ ̶w̶h̶a̶t̶ ̶i̶t̶ ̶d̶o̶e̶s̶ ̶n̶o̶t̶ ̶i̶n̶c̶l̶u̶d̶e̶ ̶w̶h̶a̶t̶ ̶y̶o̶u̶ ̶a̶r̶e̶ ̶l̶o̶o̶k̶i̶n̶g̶ ̶f̶o̶r̶ (see edit). You'll probably have to disable Lambda Proxy integration and use something like this in the mapping template:
{
"identity" : {
"sub" : "$context.authorizer.claims.sub",
"email" : "$context.authorizer.claims.email"
}
}
The mapping template "build" the event parameter of the Lambda. So you will be able to access to the parts of your claim via the event parameter.
exports.handler = (event, context, callback) => {
// TODO implement
callback(null, event.identity.email);
};
Note that I slightly modified the documentation example to avoid another confusion about what context can be:
the mapping template variable in API Gateway
the second argument of a handler in Lambda
a key of the event argument in some examples of the documentation <= I renamed it identity
Edit
As pointed out by doorstuck, the information is available using the proxy integration
Ensure you are sending the "Identity Token" as the Authorization header instead of the "Access Token".
Documentation for Identity Token
For example, I am using Amplify and was getting the access token with:
userSession.getAccessToken().getJwtToken() // Wrong
instead of
userSession.getIdToken().getJwtToken() // Correct

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