Aws-cdk: [apigateway] support cognito user pool authorizer

Created on 2 Jan 2020  路  6Comments  路  Source: aws/aws-cdk

Add support for cognito user pool authorizer - https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html

Upvote this issue if you'd like this feature in the CDK.


This is a :rocket: Feature Request

@aws-cdaws-apigateway efformedium feature-request p1

Most helpful comment

@nija-at Since there seems to be a decently solid looking implementation for this, I'm wondering if we could get an official version of it merged into CDK to save having to use these hacks/workarounds?

@0xdevalias - we absolutely can. This isn't prioritized on our end at the moment, but contributions are welcome 馃槉

All 6 comments

May be worth checking out and subscribing to https://github.com/aws/aws-cdk-rfcs/issues/95 as well.

Any progress on this feature? Any workaround in the meantime?

Thanks!

@meleksomai - you can use the the construct at the CFN layer - CfnAuthorizer - to work around this. However, you will need to configure all of the properties correctly yourself.

@nija-at , thanks. I have tried without much success.

I am following the current approach which was suggested in the following comment: https://github.com/aws/aws-cdk/issues/723#issuecomment-504753280 and it works.

I am sharing a sample of the python code which might be helpful to others.

from aws_cdk import (
    core,
    aws_lambda,
    aws_apigateway,
    aws_cognito
)

class CustomStack(core.NestedStack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:

        super().__init__(scope, id, **kwargs)

        #--------------------------------
        # API Gateway
        #--------------------------------
        custom_api = aws_apigateway.RestApi(self, "CustomRestApi",
            rest_api_name='CustomApiGateway'
            )

        # Defines the AWS Lambda resources
        custom_lambda = aws_lambda.Function(
            self, 'CustomHandler',
            runtime=aws_lambda.Runtime.PYTHON_3_8,
            code=aws_lambda.Code.asset('lambda/'),
            handler='post-handler.handler',
        )

        # Adding the resource
        custom_api_resource = custom_api.root.add_resource('management')

        # Adding the lambda to the resources
        custom_api_method_post = custom_api_resource.add_method('POST', 
            aws_apigateway.LambdaIntegration(custom_lambda, proxy=True)
        )

        # API Gateway Cognito Authorizer
        cognito_authorizer = aws_apigateway.CfnAuthorizer(
            scope=self,
            id='CustomCognitoAuthorizer',
            rest_api_id=custom_api.rest_api_id,
            name='CustomCognitoAuth',
            type='COGNITO_USER_POOLS',
            identity_source='method.request.header.Authorization',
            identity_validation_expression="Bearer (.*)",
            provider_arns=[
                'arn:aws:cognito-idp:us-XXXX-X:XXXXXXXXXX:userpool/'
                'us-XXXX-X_XXXXXXXX']
        )
        post_method_resource = custom_api_method_post.node.find_child('Resource')
        post_method_resource.add_property_override('AuthorizationType', 'COGNITO_USER_POOLS')
        post_method_resource.add_property_override( 'AuthorizerId', {"Ref": cognito_authorizer.logical_id})

The configuration is deployed but the authorization is not triggered. I am not sure if this is because I have to provide an Oauth scope when using Cognito Authorizer.

Just ran into this need myself, and since native support doesn't seem to exist yet, here was my attempted workaround for JavaScript/TypeScript using CfnAuthorizer as suggested above:

References:

import { AuthorizationType, CfnAuthorizer } from '@aws-cdk/aws-apigateway'
import { UserPool } from '@aws-cdk/aws-cognito'

const userPool = UserPool.fromUserPoolId(this, 'UserPool', userPoolId)

// ..snip..

const cognitoAuthorizer = new CfnAuthorizer(this, 'CognitoAuthorizer', {
      name: 'CognitoAuthorizer',
      type: AuthorizationType.COGNITO,
      providerArns: [userPool.userPoolArn],
      identitySource: 'method.request.header.Authorization',
      restApiId: api.restApiId,
    })

// ..snip..

Unfortunately, when trying to use this in the authorizer field of the MethodOptions (later in my code, not shown above), I get the following error:

TS2741: Property 'authorizerId' is missing in type 'CfnAuthorizer' but required in type 'IAuthorizer'.

Hacking a naive custom 'wrapper' around CfnAuthorizer to resolve the above, I came up with:

import { Construct } from '@aws-cdk/core'
import { CfnAuthorizer, IAuthorizer } from '@aws-cdk/aws-apigateway'
import { CfnAuthorizerProps } from '@aws-cdk/aws-apigateway/lib/apigateway.generated'

/**
 * Custom construct that implements a Cognito based API Gateway Authorizer.
 *
 * @see https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_author
 *
 * @see https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.CfnAuthorizer.html
 * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-authorizer.html
 * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html
 * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html
 *
 * @see https://github.com/aws/aws-cdk/issues/5618#issuecomment-666922559
 */
export class CognitoApiGatewayAuthorizer extends CfnAuthorizer implements IAuthorizer {
  public readonly authorizerId: string

  constructor(scope: Construct, id: string, props: CfnAuthorizerProps) {
    super(scope, id, props)

    this.authorizerId = this.ref
  }
}

Which can be used similar to the above example, as follows:

- import { AuthorizationType, CfnAuthorizer } from '@aws-cdk/aws-apigateway'
+ import { AuthorizationType } from '@aws-cdk/aws-apigateway'
import { UserPool } from '@aws-cdk/aws-cognito'

+ import { CustomNodeLambdaEndpoint } from './constructs/CustomNodeLambdaEndpoint'

const userPool = UserPool.fromUserPoolId(this, 'UserPool', userPoolId)

// ..snip..

- const cognitoAuthorizer = new CfnAuthorizer(this, 'CognitoAuthorizer', {
+ const cognitoAuthorizer = new CognitoApiGatewayAuthorizer(this, 'CognitoAuthorizer', {
      name: 'CognitoAuthorizer',
      type: AuthorizationType.COGNITO,
      providerArns: [userPool.userPoolArn],
      identitySource: 'method.request.header.Authorization',
      restApiId: api.restApiId,
    })

// ..snip..

Obviously this could be made 'nicer' by wrapping some of the required props into the 'wrapper' itself (eg. type), but I was looking for the simplest/closest 'api' to using CfnAuthorizer directly.


I noticed that @rrix also provided a more thorough TypeScript-based solution in https://github.com/aws/aws-cdk/issues/9023#issuecomment-658309644 which seems to implement authorizerId / support IAuthorizer.

@nija-at Since there seems to be a decently solid looking implementation for this, I'm wondering if we could get an official version of it merged into CDK to save having to use these hacks/workarounds?

@nija-at Since there seems to be a decently solid looking implementation for this, I'm wondering if we could get an official version of it merged into CDK to save having to use these hacks/workarounds?

@0xdevalias - we absolutely can. This isn't prioritized on our end at the moment, but contributions are welcome 馃槉

Was this page helpful?
0 / 5 - 0 ratings