Adding a Cognito authorizer to API Gateway with the AWS CDK

ara225

Anna Aitchison

Posted on July 16, 2020

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)

💖 💪 🙅 🚩
ara225
Anna Aitchison

Posted on July 16, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related