DEV Community

Anna Aitchison
Anna Aitchison

Posted on

Adding a Cognito authorizer to API Gateway with the AWS CDK

I recently spent days trying to figure out how to make Cognito authentication with a REST API work in the AWS CDK, to the point that I even filed a (unnecessary) bug report, so I figured I might as well make that the subject of my first dev.to post as it's pretty short and sweet.

The problem

Adding a authorizer to the API is deceptively easy. You have to use the underlying CloudFormation resource as this feature isn't fully built out in the CDK yet, but the authorizer gets added to the API in a completely normal manner with the below code.

api = aws_apigateway.RestApi(self, 'API', rest_api_name='API')
auth = aws_apigateway.CfnAuthorizer(self, "adminSectionAuth", rest_api_id=api.rest_api_id,
                                        type='COGNITO_USER_POOLS', 
                                        identity_source='method.request.header.Authorization',
                                        provider_arns=[
                                            'arn:aws:cognito-idp:...'],
                                        name="adminSectionAuth"
                                    )
Enter fullscreen mode Exit fullscreen mode

However, adding it to the method is another matter. Passing the object doesn't work - it doesn't error out initially, but the ID of the authorizer doesn't populate in the template so it fails as soon as CDK tries to create the resource.

resource = api.root.add_resource("endpoint")
lambda_function = aws_lambda.Function(self, "lambdaFunction",
                                      handler='app.lambda_handler',
                                      runtime=aws_lambda.Runtime.PYTHON_3_8,
                                      code=aws_lambda.Code.from_asset("path/to/code")
                                      )
lambda_integration = aws_apigateway.LambdaIntegration(lambda_function, proxy=True)
method = resource.add_method("GET", lambda_integration, 
                             authorization_type=AuthorizationType.COGNITO,
                             authorizer=auth
                            )
Enter fullscreen mode Exit fullscreen mode

Passing the logical_id or ref properties of the object don't work either - the authorizer parameter needs to be a object.

The Answer

It turns out that you actually have to override properties of the object to get it working, namely the troublesome AuthorizerId field that wasn't populating before.

resource = api.root.add_resource("endpoint")
lambda_function = aws_lambda.Function(self, "lambdaFunction",
                                      handler='app.lambda_handler',
                                      runtime=aws_lambda.Runtime.PYTHON_3_8,
                                      code=aws_lambda.Code.from_asset("path/to/code")
                                      )
lambda_integration = aws_apigateway.LambdaIntegration(lambda_function, proxy=True)
method = resource.add_method("GET", lambda_integration)
method_resource = method.node.find_child('Resource')
method_resource.add_property_override('AuthorizationType', 'COGNITO_USER_POOLS')
method_resource.add_property_override('AuthorizerId', {"Ref": auth.logical_id})
Enter fullscreen mode Exit fullscreen mode

I found this on GitHub in a very informative comment

Comment for #723

bgdnlp avatar
bgdnlp commented on

I know that this isn't a support forum, but this issue is one of the top results on Google. If it's inappropriate, please move it or let me know where to post.

For anyone who is looking to use the CDK, but got bitten by one of these missing features. There is an official way to work around these issues. Basically we can alter the CloudFormation resources directly. Similar concept to boto3 clients.

Here is an example of how to add an Authorizer in Python.

Assume we have an API Gateway and a POST a method:

api_gw = aws_apigateway.RestApi(self, 'MyApp')
post_method = api_gw.root.add_method(http_method='POST')
Enter fullscreen mode Exit fullscreen mode

Set the authorizer using a low level CfnResource:

api_gw_authorizer = aws_apigateway.CfnAuthorizer(
    scope=self,
    id='my_authorizer',
    rest_api_id=api_gw.rest_api_id,
    name='MyAuth',
    type='COGNITO_USER_POOLS',
    identity_source='method.request.header.name.Authorization',
    provider_arns=[
        'arn:aws:cognito-idp:eu-west-1:123456789012:userpool/'
        'eu-west-1_MyCognito'])
Enter fullscreen mode Exit fullscreen mode

Get the underlying CfnResource for the POST method created above:

post_method_resource = post_method.node.find_child('Resource')
Enter fullscreen mode Exit fullscreen mode

Set the POST method to use the authorizer by adding the required CloudFormation properties to the low level resource:

post_method_resource.add_property_override('AuthorizationType',
                                           'COGNITO_USER_POOLS')
post_method_resource.add_property_override(
        'AuthorizerId',
        {"Ref": api_gw_authorizer.logical_id})
Enter fullscreen mode Exit fullscreen mode

Take note of the second instruction, that's a dictionary. It needs to be, so that the AuthorizedId property is added correctly, like:

AuthorizerId:
  Ref: myauthorizer

instead of something like:

AuthorizerId: "Ref: myauthorizer"

As of 0.35.0, the above should output a template containing:

MyAppPOST853D1BB4:
  Type: AWS::ApiGateway::Method
  Properties:
    HttpMethod: POST
    ResourceId:
      Fn::GetAtt:
        - MyApp3CE31C26
        - RootResourceId
    RestApiId:
      Ref: MyApp3CE31C26
    AuthorizationType: COGNITO_USER_POOLS
    AuthorizerId:
      Ref: myauthorizer
    Integration:
      Type: MOCK
myauthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    RestApiId:
      Ref: MyApp3CE31C26
    Type: COGNITO_USER_POOLS
    IdentitySource: method.request.header.name.Authorization
    Name: MyAuth
    ProviderARNs:
      - arn:aws:cognito-idp:eu-west-1:123456789012:userpool/eu-west-1_MyCognito

(removed Metadata for brevity)

Top comments (4)

Collapse
 
garethfloodgate profile image
Gareth Floodgate • Edited

As of v1.88 this is now supported directly in CDK, see this commit for an example: github.com/aws/aws-cdk/pull/12786/...

TL;DR (from GitHub)

const userPool = new cognito.UserPool(stack, 'UserPool');
const auth = new apigateway.CognitoUserPoolsAuthorizer(this, 'booksAuthorizer', {
  cognitoUserPools: [userPool]
});
books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
  authorizer: auth,
  authorizationType: apigateway.AuthorizationType.COGNITO,
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
johnnyclutch profile image
Glenn A Bullock

Any idea how to add an Authorizer to a "proxied" LambdaRestApi object? One that refers all the calling paths to the lambda and allows the lambda to handle the "routing". (I'm using Nestjs in the lambda.).

I tried:

this._authLambda = new Function(this, ${ options.stackName }-auth-lambda,
{
runtime: Runtime.NODEJS_12_X,
code: Code.fromAsset("./lambdas/auth"),
handler: "main.handler",
logRetention: 7,
timeout: Duration.seconds(20),
layers: [ options.nestJsLayer ]
}
);

this._gatewayRestAPI = new LambdaRestApi(this, ${ options.stackName }-auth-api, {
handler: this._authLambda,
});

const authIntegration = new LambdaIntegration(this._authLambda);

const path = this._gatewayRestAPI.root.addResource('*');
path.addMethod("POST", authIntegration,{
authorizer: options.customAuthorizer
});

But the cdk says: "Cannot call 'addResource' on a proxying LambdaRestApi; set 'proxy' to false". I guess I could go through and add the routes of my nestjs app to the rest api, but I hate having two places to maintain them.

Collapse
 
ara225 profile image
Anna Aitchison

Hmm, been a while since I've dealt with the CDK, but I'd think there'd be another method on the REST API to do that. Alternatively, I'd suggest using the CDK to generate the cloudformation template without the offending piece, compare that to a cloudformation template doing what you want to do and use add_property_override to correct the CDK version

Collapse
 
mamhaidly profile image
mamhaidly

Link to an AWS example addressing the issue: github.com/aws-samples/aws-cdk-exa...