I'm trying to limit access on a service endpoints to a specific user pool (ie admin).
From my understanding of the documentation (https://serverless.com/framework/docs/providers/aws/events/apigateway/) this should be possible by setting
get:
handler: functions/get.main
events:
- http:
path: /
method: get
cors: true
authorizer: "arn:aws:cognito-idp:eu-west-2:####:userpool/eu-west-2_xET8A8Kui"
Deploys fine and appears correctly in the API gateway for this endpoint
Problem is any calls via AWS Amplify (React integration) to the API result in a 401 error! If I put the authorizer back to aws_iam it works fine.
Where can I even begin to debug this? I can't find any specific logs or reason why this is failing.
How you have things setup should work (I have that working - just a User Pool).
You will need to pass the idToken you received from your Amplify auth call in the Authorization header:
Authorization: Bearer eyJraWQiOiJ4T2NLWG5GakljWE...
Make sure to use the idToken, not the accessToken.
Last time I checked amplify only supported IAM Auth not user pool auth. You can use an identity pool with your user pool and still use IAM via your user pool.
Related
I have multiple Cognito user pools which I use to separate users for different applications. I also have a set of APIs which are defined in API Gateway. These APIs are common and multiple applications can use them. I am trying to control which applications have access to which APIs using a Cognito resource server and custom scopes.
This is the guide I've been following: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html
The problem I'm having is that I have to specify a user pool when creating an API Gateway authorizer. I can create multiple authorizers but I only seem to be able to select one when attaching an authorizer to an API Gateway method.
This set up means than only one user pool can ever have access to an API in API Gateway when using the Cognito authorizer. Is this correct or have I missed something?
My only option seems to be using a Lambda authorizer and doing all this manually. However, this means that I have to store a mapping between API endpoints/methods and custom scopes. If I were to take this approach, how would I verify that an access token has access to the endpoint in the incoming request?
I've done something similar but not exactly what you are doing, but maybe it'll lead you in the right direction.
So I think you're right in that you'll need to do a Lambda authorizer, but then assume the role of the cognito user. Then if that user doesn't have access to do something IAM will blow it up.
client = boto3.client('sts')
role=event['requestContext']['authorizer']['claims']['cognito:preferred_role']
assumed_role_object = client.assume_role(
RoleArn=role,
RoleSessionName='APIrole'
)
credentials=assumed_role_object['Credentials']
dynamo_resource=boto3.resource(
'dynamodb',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken'],
)
There is a solution posted here:
How to use multiple Cognito user pools for a single endpoint with AWS API Gateway?
I found Abhay Nayak answer useful, it helped me to achieve my scenario:
Allowing authorization for a single endpoint, using JWTs provided by different Cognitos, from different aws accounts. Using cognito user pool authorizer, not custom lambda authorizer.
Here is the authorizer and endpoint from my serverless .yml template:
functions:
service:
handler: service.service
events:
- http:
path: service
method: get
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
Ref: ApiGatewayAuthorizer
resources:
Resources:
ApiGatewayAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
AuthorizerResultTtlInSeconds: 300
Name: API_AUTH_cognito_authorizer
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
Type: COGNITO_USER_POOLS
ProviderARNs:
- arn:aws:cognito-idp:us-east-1:account1:userpool/userpool1
- arn:aws:cognito-idp:us-east-1:account1:userpool/userpool2
- arn:aws:cognito-idp:us-east-1:account2:userpool/userpool3
- arn:aws:cognito-idp:us-east-1:account2:userpool/userpool4
I have an API Gateway endpoint with IAM authentication, no Custom Domain Names, no API Key, API is deployed to Prod and no AWS WAF enabled (TBMK) and VPC proxy integration request method.
I am calling this endpoint from a Lambda (with attached execute-api:Invoke permission to call the API), however I am getting a 403 error with message Forbidden. Notice that if I remove the IAM authentication method, the call from Lambda works fine.
I've already seen this and this SO questions + AWS Doc on the topic but I've already tried these solutions (as explained before).
Sample code for calling API Gateway inside Lambda:
final HttpURLConnection connection = (HttpURLConnection) new URL(postApiUrl).openConnection();
connection.setRequestMethod("POST");
final int responseCode = connection.getResponseCode();
//...
How I attach API Gateway ARN to Lambda role in CDK:
this.addToRolePolicy(
new PolicyStatement({
actions: [execute-api:Invoke],
effect: Effect.ALLOW,
resources: [postMethod.methodArn],
}),
);
You have set up IAM authentication for your API GW method, but your Lambda function code does not sign the request made to API GW. Note: Simply adding the execute-api:Invoke permission to the Lambda function execution role does not sign the request.
You need to use the AWS SigV4 signing process to add the authentication information which is then verified on the API GW end. This doc lists the steps involved which basically are:
Create a canonical request.
Use the canonical request and additional metadata to create a string for signing.
Derive a signing key from your AWS secret access key. Then use the signing key, and the string from the previous step, to create a signature.
Add the resulting signature to the HTTP request in a header or as a query string parameter.
Since you're using Java, this blog post also provides some sample code which you can refer to.
APIG has a authorizer cache, check this out.
https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-403-error-lambda-authorizer/
If you could have a read and perhaps elaborate a little I'll include the proper solution.
In my AWS project, I use API Gateway to create APIs that call lambda functions. The APIs are called by an Android application. I use a Cognito user pool and a Cognito Identity pool to manage my users (authenticated and unauthenticated).
In my lambda functions, I need to get the identity ID of the users. After some research, I saw that to achieve that, I need to check Invoke with caller credentials in the Integration Request of my API.
Unfortunately, when I call my API, I got an error 500, with the following log: Execution failed due to configuration error: Invalid permissions on Lambda function. Apparently, it's because the Cognito identity pool role doesn't have the lambda invoke permissions for the backend lambda.
So in order to be able to get the identity id in my lambda function, how can I add those permissions and, if possible, what is the CloudFormation syntax to add those permissions?
Thanks for your help.
Select the AWS Lambda (in AWS UI) which you are trying to fix and scroll down to the Execution role part. You need to create a new role that has permission to access the Cognito Identity pool and sett that role for Lambda and save it.
In AWS API, have you added the authorizer which sets permission (allow/deny) to access the API.
If your users are in Cognito User Pool, there is no need of API gateway, Lambda, etc. Simply use AWS Amplify and everything is baked into the framework.
https://aws-amplify.github.io/docs/js/authentication
Robin
Here is what was missing in my CloudFormation template, just add it in the Cognito Identity Pool policy (or policies if you're dealing with unauthenticated users):
- Effect: 'Allow'
Action:
- 'lambda:InvokeFunction'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:lambda:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':function:*'
I've managed to successfull login to the API gateway I've made via my iOS device and Cognito. The problem is I'd like to use postman to test the API calls then implement them on the phone. Currently, Postman cannot authenticate (despite AWS saying it can). No matter what I do I get a 401 error (visible in the screen-shots)
What I've tried
Downloaded the postman collection from AWS Api Gateway
Then imported it into postman, and switch the authentication to "AWS Signature"
And Here is a screen shot of the Postman Generated Header Info
If I understand correctly, you are trying to call an API Gateway endpoint that is behind the built-in Cognito Authoriser.
I think you've misunderstood how you call an Cognito Authorised API Gateway:
Authorise against Cognito to get an id_token
Call API Gateway with the Authorization header set to id_token
Renew id_token every hour
By enabling ADMIN_NO_SRP_AUTH you're allowing the first step (sign-in to Cognito) to be simplified so that you can more easily do it manually. (If you hadn't, then you would need to do SRP calculations).
One way to get the id_token is to use the aws cli (further ways are shown in the documentation):
aws cognito-idp admin-initiate-auth --user-pool-id='[USER_POOL_ID]' --client-id='[CLIENT_ID]' --auth-flow=ADMIN_NO_SRP_AUTH --auth-parameters="USERNAME=[USERNAME],PASSWORD=[PASSWORD]"
You can then use the result (AuthenticationResult.IdToken) as the Authorization header in Postman (no need for the AWS v4 signature- that is only for IAM authentication).
n.b. a much fuller explanation with images can be found here.
Here is what I finally did to fix postman auth issues
1) Turned off App Client Secret in the Cognito pool.
2) Ran aws --region us-east-1 cognito-idp admin-initiate-auth --cli-input-json file://gettoken.json
JSON file example
{
"UserPoolId": "us-east-1_**********",
"ClientId": "******************",
"AuthFlow": "ADMIN_NO_SRP_AUTH",
"AuthParameters": {
"USERNAME": "*********",
"PASSWORD": "***********"
}
}
3) Went to Postman > Authorization > Bearer Copied the idToken value into the token field and everything worked.
NOTE: For those wondering if not using a secret client key is safe. See this article.
How to setup cross account custom authorizer with serverless framework? Custom authorizer configured with sls framework works fine if it is in the same AWS account as function that needs authorization.
What I have now is organization root account where authorizer function has been deployed. On the second account, which is organization member, I have a serverless service deployed with the endpoints that needs to be authorized from the root account.
Is it possible to configure something like this inside serverless.yml that will be deployed on the member account (111111111111 is root account number):
hello:
handler: api/hello.handler
events:
- http:
path: hello
method: get
cors: true
authorizer: arn:aws:lambda:eu-west-1:111111111111:function:authorizer
I have tried this and received following error:
An error occurred: AuthorizerApiGatewayAuthorizer - The policy of
Lambda function must explicitly authorize the method or custom
authorizer with a SourceArn condition for cross account integration
(Service: AmazonApiGateway; Status Code: 400; Error Code:
BadRequestException;
... which makes sense according to the AWS docs. These docs explains how to manually do it using API Gateway console which is exactly what I did for now (authorizer in the root, authorizer in the member account - manually connected through API gateway, same as described in the docs).
I need a better solution as the number of services and organization member accounts is going to grow.
Is it possible to configure and make this work with serverless framework?
As with a lot of the Serverless Framework, there's a plug-in for those times that CloudFormation hasn't yet offered an option:
https://github.com/rschick/serverless-plugin-lambda-account-access
The custom authorizer's serverless.yml should then include:
plugins:
- serverless-plugin-lambda-account-access
provider:
allowAccess:
- 111111111111 # account id of invoking account