AWS API Gateway: Authorize Cognito user groups - amazon-web-services

I am new to the AWS world of API Gateway, and am trying to limit access to my APIs by user group. To clarify I can already run authenticated API, the question is around authorization (limit a group of users to API-1, and another group to API-2). I am using HTTP-API and I do not wish to use others (to save cost).
I have created a Cognito user-pool and created two groups called 'regular' and 'admin'. I have also setup an app-client.
Next I have two API routes, that map to two different Lambda functions (just hello world). These work perfectly without authentication, and also with authentication. I am using JWT-auth with Cognito, and for multiple reasons, this is the right approach for my app.
The trouble is how do I restrict access to the API, for the relevant group of users. Hence, only the users in the admin-group should be able to use the admin-api. I believe the section in red can help, but I cant seem to find the right documentation. I read that I can also create a lambda function to authorize users, but that seems like a waste, why pay for another lambda function, if the restrictions can be applied here.
Would appreciate any help.

Application scopes are not what are looking for (they define application access, not user access).
This is broadly referred to as Role-based Access Control (RBAC). The built in authorizer doesn't have any support for it so you'll have to either check the role/group in you handler or opt for a custom authorizer.

Scopes are not the right thing to use here. Scopes work well if you have different app clients (actual applications using your API Gateway) to limit the scope of what endpoints they can access. They don't work for role based access.
As you say, you already use Cognito User Pool and have some user groups setup. A simple apporach would be to use a post confirmation Lambda trigger. This Lambda is triggered whenever a new user has been confirmed. It even gets triggered when you use an external identity provider and a user logs in the first time with Cognito hosted UI for instance. In the simplest case you add a user to your 'regular' group with the post confirmation Lambda.
For authentication and authorization on your API Gateway routes, you replace JWT authorizer with a custom authorizer. This means, you will use a Lambda function to do the authentication and authorization. Within the Lambda function you must verify the JWT token. If the JWT token or the request itself is invalid you throw an exception with the message "Unauthorized". API Gateway will translate this to a 401 "Unauthorized" response. If the JWT token is valid, you decode it and get the cognito:groups claim out of it. You can decode your token and look at the claims on jwt.io for testing. In you Lambda function, you then check in what groups your user is and if the group has access to a specific route. The route the user tries to access is also part of the event triggering your Lambda function.
# dummy python code
if route.startswith('/adminuser') and 'admin' in cognito_groups:
return { 'isAuthorized': True, 'context': { 'custom_key': 'custom_value' } }
# respond with 403 "Forbidden"
return { 'isAuthorized': False }
The payload for the Lambda input and output can be found in the HHTP API Lambda Authorizer documentation.
For a deeper dive, have a look into "building fine-grained authorization using Amazon Cognito, API Gateway, and IAM". For a more advanced look into authorization options, I recommend this video from the re:invent 2017.

One idea is to create a different Cognito Userpool for each group. Then within each pool, define a Resource Server and put the group name as a scope. This way the group is available in the scope. Then, make a combined Cognito authorizer as follows.
securitySchemes:
CognitoAuth:
type: apiKey
name: Authorization
in: header
x-amazon-apigateway-authtype: cognito_user_pools
x-amazon-apigateway-authorizer:
type: cognito_user_pools
providerARNs:
- 'arn:aws:cognito-idp:{region}:{account}:userpool/{pool1}'
- 'arn:aws:cognito-idp:{region}:{account}:userpool/{pool2}'
- 'arn:aws:cognito-idp:{region}:{account}:userpool/{pool3}'
You are then free to put the group names (as scopes) globally or in any path.
Furthermore, you will have more control over the policies (e.g., custom attributes, MFA, sign-up options) for each group by virtue of having used different pools.

Related

AWS Cognito + API Gateway, Authorize based on username

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?.

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

Using AWS Lambda Authorizer in API Gateway

I have my WEB API's hosted in Docker. My Angular client will send a JWT token to access any of these API's. I wanted to make use of AWS API Gateway feature to add an Authorization check before calling the API client requested. From the docs I see that we can leverage the Lambda Authorizer concept to Achieve this. But then again I though why using Lambda Authorizer when I can come up with an DOT NET CORE API which can validate the user.
Does my Lambda Gateway makes sense for my case?
If it does, what would be the output of the lambda Authorizer? A simple true/false which says the the Token is valid or not?
I see that this is what the response should/might look like. How this should translate to in my case
{
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Resource": [
"arn:aws:execute-api:us-east-1:1234567:myapiId/staging/POST/*"
],
"Effect": "Allow"
}
]
},
"principalId": "Foo"
}
What should happen in API gateway after the Lambda Authorizer executed ? Who calls my actual API which is requested by the client?
If you are using a Lambda Authorizer, returning an Allow or Deny Policy is what you are looking for.
This essentially grants API Gateway permissions to invoke the underlying target. I know it sounds weird at a first glance, but that's how it works. Think of an Allow policy as a true return statement (credentials matched) kind of thing whilst a Deny policy is more of a false return statement (credentials didn't match / not enough permissions based on your rules, etc).
To get you off ground, you can simply copy/paste the code available at the docs and modify the authentication way to your liking (the docs show an example using a header with Allow or Deny values, which is definitely not what you want, that's just meant for the sake of an example).
So, back to your question by enumerating all the answers:
Yes, but it's called a Lambda Authorizer instead of a Lambda Gateway
Either an Allow or Deny policy for valid/invalid tokens respectively.
If the Lambda Authorizer responds with an Allow policy, it will then invoke the target (which can be a Lambda function, an SNS Topic, an HTTP endpoint - this is likely your case - and so on). The authorizer will just act as an interceptor and decide whether to proxy the call to the target or not.
From what I understood as per the question, you want to validate the user who are calling your API.
You can do it in all the ways you have already mentioned.
Using Lambda Authorizers, you will get a 200 or a 403 code not true false. You can follow the following link to set up your authorizer :
https://blog.codecentric.de/en/2018/04/aws-lambda-authorizer/
You can also use AWS Cognito for managing your users, it'll simplify your work a lot.
You just add your dotnet core api to "Integration Request" tab, Choose Integration type as HTTP and mention the dotnet core api in Endpoint URL field
Amazon Cognito lets you add user sign-up, sign-in, and access control to your web and mobile apps quickly and easily. Amazon Cognito scales to millions of users and supports sign-in with social identity providers, such as Facebook, Google, and Amazon, and enterprise identity providers via SAML 2.0.
Advantages for using Cognito:
Managed service, less components to implement/monitor/scale
Easily configurable via portal, CLI and templates
Supports multiple flows for authentication (client side, server side, OAuth2, custom)
Supports Lambda triggered functions on authentication/registration events
Uses JWT signed tokens which can be passed directly to clients in session cookies and used to verify requests and passed in related API calls so a single authentication/authorisation method can be used through your stack statelessly Group membership, supplied in access token can be used for authorisation (e.g. users in group “Admin” can perform admin functions)
Handles:
User group membership and attribute storage
Email/Phone verification
User invitation
Login/Signup UI forms (customisable)
Password reset
Disadvantages:
Less control over authentication/authorisation (limits to UI/flow customisation)
Potential for lock-in (cannot export users with passwords for migration)
Amazon Cognito has three type of authorizer
Amazon Cognito user pool - User pool authorizer.
Amazon Cognito federated identities - AWS IAM Authorization.
Custom Lambda identity providers - Custom Autorizer

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?

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.