is the Authorization header passed to lambda function by Cognito Authorizer? - amazon-web-services

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.

Related

How to get the USER details from a call to HTTP API Gateway + Lambda that is authenticated with an Amazon Cognito User Pool

USER logs in in Amazon Cognito and the App/Web gets an "Access Token" that is used whenever it calls API Gateway (HTTP API or REST API).
The API Gateway is configured to use Cognito User Pool as Authorizer, so if the "Access Token" is valid the call can pass to Lambda.
So, how do I know (inside Lambda) who is the user doing the calls and what are his/her details?
This answer set me on an easy path, but I realized there is an easier one.
If you dump the content received by the Lambda (python code) like this.
And then call the API like this (GET/POST/other depends on how you defined your API)
# curl --request GET --header "Authorization: ${TOKEN}" "${GATEWAY_URL}"
You get a bunch of text. Paste it on https://jsonformatter.curiousconcept.com/ to format it nicely and you will see that within the event you have lots of info like
{
"version":"2.0",
"routeKey":"ANY /*****",
"rawPath":"/default/****",
"rawQueryString":"",
"headers":{
"accept":"*/*",
"authorization":"eyJraW...QiOi",
"content-length":"0",
"host":"*******.execute-api.eu-west-1.amazonaws.com",
"user-agent":"curl/7.52.1",
"x-amzn-trace-id":"Root=1-5ff1***eee7347",
"x-forwarded-for":"**.**.243.124",
"x-forwarded-port":"443",
"x-forwarded-proto":"https"
},
"requestContext":{
"accountId":"****",
"apiId":"*****",
"authorizer":{
"jwt":{
"claims":{
"at_hash":"-pO***Eg",
"aud":"1jk***0n0",
"auth_time":"160***928",
"cognito:username":"357d***a77de4d",
"email":"***#gmail.com",
"email_verified":"true",
"event_id":"19f7e***dc0e80",
"exp":"16***28",
"iat":"16***928",
"iss":"https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_***8z",
"name":"UserName",
"sub":"357***77de4d",
"token_use":"id"
},
"scopes":"None"
}
},
"domainName":"***.execute-api.eu-west-1.amazonaws.com",
"domainPrefix":"***",
"http":{
"method":"GET",
"path":"/default/***",
"protocol":"HTTP/1.1",
"sourceIp":"***",
"userAgent":"curl/7.52.1"
},
"requestId":"Yi***sA=",
"routeKey":"ANY /***",
"stage":"default",
"time":"02/Jan/2021:23:45:15 +0000",
"timeEpoch":1609631115197
},
"isBase64Encoded":false
}
That is, all the user details are there and you don't have to do anything to get them.
In particular, these 2 are here
event["requestContext"]["authorizer"]["jwt"]["claims"]["email"]
event["requestContext"]["authorizer"]["jwt"]["claims"]["name"]
This has even better implications.
You can leverage on HTTP API Gateway + Lambda for:
Validation of an Access TOKEN (if not valid or expired you get an 'Unauthorized' response)
Extraction of token scopes / details
All this within 130ms (of which you only pay for 3ms). No loading libraries, no manipulating data, no manual decoding, no bulls**t.
The only required work is to configure the User Pool and API Gateway (HTTP or REST type) to do the Authentication for you.
If you wonder where you get the value of TOKEN, it is encoded on the URL that is called after a successful login to Cognito.
Note that you may get in the response URL 2 tokens, ID_TOKEN and ACCESS_TOKEN. To get the user details you need to call the API using the ID_TOKEN.

How can I get my Auth0 permissions into the scope claim of the access token for an AWS HTTP API Jwt Authorizer?

I am trying to get an AWS HTTP API JWT Authorizer with scopes on an endpoint to work happily with my Auth0 access tokens.
The JWT Authorizer looks for the necessary scopes in the access token's "scope". I am thinking that this is used for fine-grained authorization. But, Auth0 returns permissions in a "permissions" array rather than in the token's "scope".
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions? Or will I need to have my lambda function dissect the authenticated JWT after its gone past the JWT Authorizer?
There is a way to add your user permissions into the scope claim of your token. This thread details two ways to achieve this, thread:
Adding a rule that reads these permissions and copies them into your access_token scope claims.
function (user, context, callback) {
var ManagementClient = require('auth0#2.17.0').ManagementClient;
var management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
var params = { id: user.user_id};
management.getUserPermissions(params, function (err, permissions) {
var permissionNames = [];
permissions.forEach(function(obj) { permissionNames.push(obj.permission_name); });
if (err) {
// Handle error.
}
context.accessToken.scope = permissionNames;
callback(null, user, context);
});
}
Using the RBAC feature with TOKEN_DIALECT. Note that information around this is extremely scarce, this post linked below it the only piece of information I have found about it. Also I could not get this to work consistently so personally I use the method #1 listed here. TOKEN_DIALECT
In an ideal world option #2 is really the best and has the least configuration, but I have had issues with this.
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions?
This is a bad idea. A JWT token is small (<8 kb according to this answer). What will happen when you have a million resources? Will your "array of permissions" have a million items, too?
https://auth0.com/blog/on-the-nature-of-oauth2-scopes/
scopes are used to express what an application can do on behalf of a given user. (...) scopes are used for handling delegation scenarios (...) overloading scopes to represent actual privileges assigned to the app (as opposed to the delegated permissions mentioned above) is problematic
I work for Auth0 and we're building a solution to handle fine-grained authorization. See https://zanzibar.academy/

Use ApiGateway Authorizer to Validate Github Payload Signature (X-Hub-Signature)

I am currently working on a simple api to receive Github event payloads, and I want to validate that they are coming from the correct source. With this I am working to use the hmac signature in the requests header (generated by github using a secret provided by me). To validate the signature, the ApiGateway authorizer requires the signature (X-Hub-Signature), the secret used to generate the signature, and the body of the message. As far as I can tell, Api Gateway does not allow you to pass the body to an ApiGateway Authorizer. Does anyone know a way around this that does not require additional proxy lambdas and s3?
*Note: The requester is the Github Webhook service (not able to add body to header)
Basic ApiGateway Auth Docs:
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
Here is how you do it.
Pass your content to Authorization header of your incoming request, It will get delivered to your custom Authorizer.
Grab the contents of the from the below attribute,
event.authorizationToken
where event is one of the parameters (1st) passed to lambda,
I currently encrypt and add all the info to that header and gets delivered to the Custom Authorizer lambda.
You can also access additional parameters as below in your custom Authorizer lambda:
var headers = event.headers;
var queryStringParameters = event.queryStringParameters;
var pathParameters = event.pathParameters;
var stageVariables = event.stageVariables;
var requestContext = event.requestContext;
Hope it helps.

Amazon AWS getAttribute() using AWS.config.credentials

I have just started with Amazon Cognito and I want to use it for my web application. I want to develop a stateless app, in which I SignUp/SignIn using Cognito and then using the JWT token in rest of the requests.
I have implemented sign-up and sign-in flow in Node.js using amazon-cognito-identity-js package and then using the JWT token to call a lambda function using aws-sdk. Things are as expected till here.
But now the issue is with different user operations like get attribute, verify attribute, update password etc. as listed #
https://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html
All of these operations require cognitoUser object and in the documentation they are using userPool.getCurrentUser(); expression.
And I have read somewhere that this method returns the last authenticated user. So I think this expression userPool.getCurrentUser(); will cause conflicts. For example if UserB logs in after UserA and UserA tries to update his password, it will not work.
Can someone suggests me what are the possible solutions?
Should I store the cognitoUser object in session at server side ?
[This solution breaks my stateless requirement and I will have to maintain session on server side.]
Is there any way to perform these operations using JWT token ?
Please suggest if you can think of any other better approach to implement Cognito in web app.
Thanks.
We have a stateless app using cognito and lambdas.
The way we have set it up is to not call lambdas directly but to use Api Gateway and lambda-proxy integration.
If you call lambdas directly from your front end code and are using the cognito tokens for authentication then you need to put a lot of logic in each lambda to validate the token, e.g. download the relevant keys, check the signature of the jwt, timestamps, issuer etc. If you use API gateway then you can just create a cognito authorizer and place it in front of your lambdas.
We pass the id_token when making api calls, then the call is validated by the authorizer and the lambda receives all the current attributes set up in the user pool. This means we don't need to make additional calls to get attributes.
For changing the user passwords this can be done from the front-end of the app by calling the cognito api with the access_token if you have allowed it in the user pool client setup.

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