AWS Cognito + API Gateway, Authorize based on username - amazon-web-services

I have an API Gateway HTTP API. For some of the routes I've attached a Cognito User Pool authorizer which works fine. However now I want to add a restriction to the authorization so that only the user with the correct username can access a certain API.
For example, if the route is PUT /users/{username}, I only want the user with the corresponding username to be able to edit his/her profile information. For other users, it should be unauthorized.
This seems like a common use case, how do I do this? I've looked into scopes but that doesn't seem to be the solution. Would I have to write a custom Lambda authorizer for this, instead of using the Cognito authorizer? If so could you provide an example, since I have no experience writing the Lambda authorizer.

You can use a Mapping Template to structure a request in API Gateway, and use the information stored in a Cognito token from there https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference.
For your specific request to /users/{username}, you could use an if to only provide the desired information if the user matches How to use IF condition in AWS Api gateway mapping templates. can i use if condition without using foreach?.
Alternatively, you may be able to simplify the path to PUT to /users, then pull the username from the cognito token and use it in the mapping template How can I access the Cognito username of the caller in a Lamda function?.

Related

using api gateway with aws cognito for protected routes

So I'm going to put a public facing API up using AWS API Gateway, where I'll have back end lambda resources that handle the logic for each route (decoupled microservice).
What should I be storing in the JWT? Currently, I've disabled all read attributes, so the token only contains cognito:username, where in my database I will store this as the user id for each user. My understanding is that once a JWT is properly generated, I can use Cognito as an authorizer with API Gateway, and then once the token JWT details are received at the lambda layer, all I need to do is use the cognito:username key to lookup the user profile in my database.
Should I be implementing any other checks in the backend, or is it safe to rely on API gateway to pass the authenticated request?
Thanks!
The cognito API Gateway authorizer will only check if the token has not expired and if it belongs to the correct user pool. But since you will be extracting username from the token itself, you should be safe. Just make sure to configure API Gateway to pass Authorization header to the lambda, it does not do this by default.

How to Authorize APIGateway calls to a Lambda Function?

I have an API Gateway which is connected to a Lambda Function. And In the Method Request for a particular POST Method, I want some restricted people only to be able to call the Method.
One way I can implement that is by explicitly passing a token in the request body which I can provide to every authenticated user and then checking if a token is present in the method. Also, I saw Authorization : AWS_IAM in the Method Request details.
I am new to AWS and cannot figure out how to call the API with AWS_IAM authorization via an external Application using the URL we get after deploying the API ?
I want some restricted people only to be able to call the Method
One way of doing this is by means of API keys:
API keys are alphanumeric string values that you distribute to application developer customers to grant access to your API.
In your question you wrote about "explicitly passing a token in the request body" but it was not clear if you want to implement such a solution yourself, or use the solution provided by API Gateway (i.e. API keys)
The IAM authentication for API Gateway APIs will require to create IAM group or IAM users for those "restricted people" in your AWS Account. General steps for that are explained here:
Control access for invoking an API
How do I enable IAM authentication for API Gateway APIs?
I suggest to do this using the API Gateway Authorizers and create a Cognito Authorizer as you are already using a token of the Authenticated user you can achieve this by:
Go to your API gateway select Authorizers from the left menu.
Click on create Authorizer.
After clicking create Authorizer you will have the below screen that will give you the ability to add your existing Cognito user pool and add "Authorization" as token source.
After setting up your Authorizer you will be able to use the "idToken" returned by Cognito after an authentication and pass it in your API request as Authorization header(BEARER token).
Click on your Resources in your API Gateway and choose your lambda function and under Method Request you will be able to assign your created authorizer under (Settings -> Authorization).

Use custom authorization logic with AWS Cognito authentication

We have a Cognito User Pool which contains the users we would like to allow access to, to our API. Our API uses Lambda functions to service the endpoints. The Cognito pool is configured with a custom field roles which is essentially a comma-separated list of roles that user possesses.
Now, first, we want the Cognito authentication to take place (to determine whether the user does belong to our pool and the credentials are valid). Then, we somehow want to run our custom logic to run which will look at the roles field which it will receive through the claims, and then allow or deny the request based on internal business logic.
We tried using a custom authorizer to implement this logic and set it as the authorizer for our endpoints. Then, we enabled Cognito authorization for this authorizer function. The problem is that since Cognito protects API endpoints and not lambda functions per se, the Cognito authorization simply does not run when an API endpoint is hit and the custom authorizer is called.
How do we achieve our objective of using custom logic with Cognito authorization? Any help in this regard would be highly appreciated.
All of the claims in the users' token are available in the context that can be passed to your lambda function if you are using cognito authorizers under $context.authorizer.claims.property Would mapping that claim into your lambda function and checking that the roles is present at the beginning of your lambda work for you?

Public API how to discriminate authenticated user vs anonymous user

Suppose I have an AWS Lambda and a related API Gateway endpoint API (e.g. /user/{userid}/activities).
This API can be invoked from both authenticated and unauthenticated users, and based on this, acts differently.
Authenticated users are users that owns a valid JWT. Unauthenticated users, haven't a JWT or their JWT is expired or it's invalid.
This JWT is validated by a Custom Authorizer who make validation based on token's signature public key and other parameters (expire date, issuer and so on).
The lambda related to /user/{userid}/activities needs to know if the custom authorizer had validated (or not) the related call and so can choose what behaviour should be used.
How can I do that?
Approach depends on the way you distinguish anonymous and authenticated users.
Given that you want to identify anonymous users when no authorization header is specified, AWS APIGateway authorizers will always deny access since the header is missing.
You could consider following options:
Always use a token (for both anonymous/authenticated users).
Move authorization logic to a lambda wrapper (so you won't be using custom authorizer for these endpoints).
Proxy through a router so you get an opportunity to add authorization header if missing (with this no client changes, server add a predefined token for anonymous users).
Either way you will need to change the endpoint signature to something like: /user/activities and infer userId from the context, context can be updated with a proper userId post verification by the authorizer.

Is it safe to authenticate a Cognito User through API Gateway to Lambda using a custom property?

I'm currently using a Cognito User Pool as an authorizer for an API Gateway endpoint, through to a Lambda function.
Can I pass the Integrated Request on to Lambda and SECURELY allow or deny from inside Lambda based on a custom attribute?
Mapping:
"administrator" : "$context.authorizer.claims['custom:administrator']",
Lambda handler:
boolean isAdmin = Boolean.parseBoolean(request.getContext().get("administrator"));
if(isAdmin) etc...
To be clear, a user that is NOT an administrator should not have access to the same API endpoints that and Administrator does..
Do I need to do anything else before/after this point?
I am sending the initial request to the API Gateway with Javascript after the user has logged into Cognito, by including the Authorization: JWToken header.
Do I need to verify the signature of the token in the Lambda function? I presume that API Gateway has already done that.
Is there a better way to manage this in terms of security?
Ideally I would like to be able to limit access to the API Endpoint based on GROUPS within the User Pool, however I don't think this is possible.
The Groups documentation talks about limiting access/permissions via AWS Identity and Access Management. If I go down this path, how do I make a request to the API Gateway? Do I still use the JWToken Authorization header, and use Cognito as the Authorizer in API Gateway?
The custom attribute/claim support included with Cognito user pools is secure and can be used for use cases such as this when used correctly. There are a couple of caveats.
First, ensure that users aren't able to modify the custom attribute themselves. When adding the customer attribute, do not mark the attribute as mutable. Also, custom attributes can be can be marked as readable or writable for each application. For this use case, you'll want to set the attribute as readable for the application the users have access to. Details about custom attributes can be found here.
The other caveat is to ensure that your request body can never by-pass your mapping template which could allow an attacker a way to directly set the administrator attribute being passed to your Lambda function. To do this, edit your integration request and set "Request body passthrough" to "Never".
There are other alternatives you could use for this use case. The cleanest approach is to provide a completely separate API for your administrators. Then you can use a separate Cognito user pool for your administrators, or you could use IAM users or groups.