Ivan Kluzak
Ivan Kluzak

Reputation: 777

AWS CDK user pool authorizer

I'm trying to create an API gateway using the AWS-CDK and protect the REST endpoints with a Cognito user pool authorizer.

I cannot find any examples how one would do this. I thought it should look something like this but maybe the methods I need do not exist?

const cdk       = require('@aws-cdk/cdk');
const lambda    = require('@aws-cdk/aws-lambda');
const apigw     = require('@aws-cdk/aws-apigateway');

const path  = require('path');

// 
// Define the stack:
class MyStack extends cdk.Stack {
    constructor (parent, id, props) {
        super(parent, id, props);    

        var tmethodHandler = new lambda.Function(this, 'test-lambda', {
            runtime: lambda.Runtime.NodeJS810,
            handler: 'index.handler',
            code: lambda.Code.directory( path.join( __dirname, 'lambda')),
        });

        var api         = new apigw.RestApi(this, 'test-api');

        const tmethod   = api.root.addResource('testmethod');

        const tmethodIntegration    = new apigw.LambdaIntegration(tmethodHandler);

        tmethod.addMethod('GET', getSessionIntegration, {
            authorizationType: apigw.AuthorizationType.Cognito,
            authorizerId : 'crap!!!?'
        });

    }
}

class MyApp extends cdk.App {
    constructor (argv) {
        super(argv);

        new MyStack(this, 'test-apigw');
    }
}

console.log(new MyApp(process.argv).run());

Upvotes: 22

Views: 10249

Answers (9)

Shariq Anwar
Shariq Anwar

Reputation: 111

If you are currently using CDK v2 or CDK v1(latest). This has been made easy:

// Your cognito userpool
const userPool = new cognito.UserPool(this, "MyUserPool")

// Authorizer for your userpool
const cognitoAuthorizer = new apigw.CognitoUserPoolsAuthorizer(
  this,
  "CognitoAuthorierOnLambda",
  {
    cognitoUserPools: [userPool],
  }
);

// Rest Api
const api = new apigw.RestApi(this, 'myApi', {
  defaultCorsPreflightOptions: {
    allowOrigins: apigw.Cors.ALL_ORIGINS,
    allowMethods: apigw.Cors.ALL_METHODS, // this is also the default
  },
  deploy: true
});

// add /getItems endpoint
const getItems = api.root.addResource('getItems');

// attach lambda to this endpoint 
const getItemsIntegration = new apigw.LambdaIntegration(getItemsLambdaFunction);    

// make the endpoint secure with cognito userpool           
getItems.addMethod('GET', getAllItemsIntegration, {
  authorizer:cognitoAuthorizer
});

Upvotes: 1

Wingjam
Wingjam

Reputation: 802

As of v1.88 this is now supported directly in CDK

const userPool = new cognito.UserPool(this, 'UserPool');

const auth = new apigateway.CognitoUserPoolsAuthorizer(this, 'booksAuthorizer', {
  cognitoUserPools: [userPool]
});

declare const books: apigateway.Resource;
books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
  authorizer: auth,
  authorizationType: apigateway.AuthorizationType.COGNITO,
});

AWS CDK API V1 Reference

AWS CDK API V2 Reference

Upvotes: 2

jokoso
jokoso

Reputation: 441

For the weirdos using the Java version of the CDK (like me), you can utilize the setters on the Cfn constructs:

final UserPool userPool = ...
final RestApi restApi = ...
final LambdaIntegration integration = ...
final Method method = restApi.getRoot().addMethod("GET", integration);

final CfnAuthorizer cognitoAuthorizer = new CfnAuthorizer(this, "CfnCognitoAuthorizer",
        CfnAuthorizerProps.builder()
                .name("CognitoAuthorizer")
                .restApiId(restApi.getRestApiId())
                .type("COGNITO_USER_POOLS")
                .providerArns(Arrays.asList(userPool.getUserPoolArn()))
                .identitySource("method.request.header.Authorization")
                .build());

final CfnMethod cfnMethod = (CfnMethod) method.getNode().getDefaultChild();
cfnMethod.setAuthorizationType("COGNITO_USER_POOLS");
cfnMethod.setAuthorizerId(cognitoAuthorizer.getRef());

Upvotes: 2

Henry Sisalema
Henry Sisalema

Reputation: 61

You have to:

  • create the api gateway
  • set Cognito as authorizer in the api gateway
  • set the authorization in your method
  • set your integration with the lambda to 'Use Lambda Proxy integration'. The LambdaIntegration properties has on true this value by default, so don't worry for it

Finally, make a request adding the token in the Header. The API gateway will validate it with Cognito. If this pass then, your lambda will be triggered and in the event you can find the claims event.requestContext.authorizer.claims.


const lambda = require("@aws-cdk/aws-lambda");
const apiGateway = require('@aws-cdk/aws-apigateway'); 

 const api = new apiGateway.RestApi(
      this,
      '<id-ApiGateway>',
      {
        restApiName: '<ApiGateway-name>',
      },
    );

    const auth = new apiGateway.CfnAuthorizer(this, '<id>', {
      name: "<authorizer-name>",
      type: apiGateway.AuthorizationType.COGNITO,
      authorizerResultTtlInSeconds: 300,
      identitySource: "method.request.header.Authorization",
      restApiId: api.restApiId,
      providerArns: ['<userPool.userPoolArn>'],
    });

    const myLambda= new lambda.Function(this, "<id>", {
      functionName: '<lambda-name>',
      runtime: lambda.Runtime.NODEJS_10_X,
      handler: "<your-handler>",
      code: lambda.Code.fromAsset("<path>"), // TODO: modify the way to get the path
    });

      const lambdaIntegration = new apiGateway.LambdaIntegration(myLambda);

      const resource = api.root.resourceForPath('<your-api-path>');
      // When the API will be deployed, the URL will look like this
      // https://xxxxxx.execute-api.us-east-2.amazonaws.com/dev/<your-api-path>

      const authorizationOptions = {
        apiKeyRequired: false,
        authorizer: {authorizerId: auth.ref},
        authorizationType: 'COGNITO_USER_POOLS'
      };

      resource.addMethod(
        GET, // your method
        lambdaIntegration,
        authorizationOptions
      );

Upvotes: 4

amwill04
amwill04

Reputation: 1360

As of September 2019 @bgdnip answer doesnt translate exactly for typescript. I got it working with the following:

const api = new RestApi(this, 'RestAPI', {
    restApiName: 'Rest-Name',
    description: 'API for journey services.',
});

const putIntegration = new LambdaIntegration(handler);

const auth = new CfnAuthorizer(this, 'APIGatewayAuthorizer', {
    name: 'customer-authorizer',
    identitySource: 'method.request.header.Authorization',
    providerArns: [providerArn.valueAsString],
    restApiId: api.restApiId,
    type: AuthorizationType.COGNITO,
});

const post = api.root.addMethod('PUT', putIntegration, { authorizationType: AuthorizationType.COGNITO });
const postMethod = post.node.defaultChild as CfnMethod;
postMethod.addOverride('Properties.AuthorizerId', { Ref: auth.logicalId });

This is from https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_resource_props

UPDATE October

The above is already out of date and unnecessary and can be achieved with the following with aws-cdk 1.12.0

const api = new RestApi(this, 'RestAPI', {
    restApiName: 'Rest-Name',
    description: 'API for journey services.',
});

const putIntegration = new LambdaIntegration(handler);

const auth = new CfnAuthorizer(this, 'APIGatewayAuthorizer', {
    name: 'customer-authorizer',
    identitySource: 'method.request.header.Authorization',
    providerArns: [providerArn.valueAsString],
    restApiId: api.restApiId,
    type: AuthorizationType.COGNITO,
});

const post = api.root.addMethod('PUT', putIntegration, {
    authorizationType: AuthorizationType.COGNITO,
    authorizer: { authorizerId: auth.ref }
});

Upvotes: 23

Ivan Kluzak
Ivan Kluzak

Reputation: 777

I figured out what looks like a mechanism... I was able to get it to work like this:

var auth = new apigw.cloudformation.AuthorizerResource(this, 'myAuthorizer', {
    restApiId: api.restApiId,
    authorizerName: 'mypoolauth',
    authorizerResultTtlInSeconds: 300,
    identitySource: 'method.request.header.Authorization',
    providerArns: [ 'arn:aws:cognito-idp:us-west-2:redacted:userpool/redacted' ],
    type: "COGNITO_USER_POOLS"
});

tmethod.addMethod('GET', getSessionIntegration, {
    authorizationType: apigw.AuthorizationType.Cognito,
    authorizerId : auth.authorizerId
});

Now to figure out how to enable CORS headers on API Gateway...

Upvotes: 3

dorival
dorival

Reputation: 51

This is my solution in TypeScript (based somewhat on bgdnlp's response)

import { App, Stack, Aws } from '@aws-cdk/core';
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda';
import { LambdaIntegration, RestApi, CfnAuthorizer, CfnMethod } from '@aws-cdk/aws-apigateway';

const app = new App();
const stack = new Stack(app, `mystack`);
const api = new RestApi(stack, `myapi`);

const region = Aws.REGION;
const account = Aws.ACCOUNT_ID;
const cognitoArn = `arn:aws:cognito-idp:${region}:${account}:userpool/${USER_POOL_ID}`;

const authorizer = new CfnAuthorizer(stack, 'Authorizer', {
  name: `myauthorizer`,
  restApiId: api.restApiId,
  type: 'COGNITO_USER_POOLS',
  identitySource: 'method.request.header.Authorization',
  providerArns: [cognitoArn],
});

const lambda = new Function(stack, 'mylambda', {
  runtime: Runtime.NODEJS_10_X,
  code: Code.asset('dist'),
  handler: `index.handler`,
});

const integration = new LambdaIntegration(lambda);

const res = api.root.addResource('hello');

const method = res.addMethod('GET', integration);

const child = method.node.findChild('Resource') as CfnMethod;

child.addPropertyOverride('AuthorizationType', 'COGNITO_USER_POOLS');

child.addPropertyOverride('AuthorizerId', { Ref: authorizer.logicalId });

Upvotes: 5

bgdnlp
bgdnlp

Reputation: 1145

The previous answers no longer work because the authorizerId property was replaced with authorizer, which isn't fully implemented at this time.

Instead, it can be done by using the underlying CfnResource objects, as described in the official guide.

Here's Python code as an example:

from aws_cdk import cdk
from aws_cdk import aws_apigateway


class Stk(cdk.Stack):
    def __init__(self, app, id):
        super().__init__(app, id)

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

        # Create authorizer using 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'])

        # Get underlying post_method Resource object. Returns CfnMethod
        post_method_resource = post_method.node.find_child('Resource')
        # Add properties to low level resource
        post_method_resource.add_property_override('AuthorizationType',
                                                   'COGNITO_USER_POOLS')
        # AuthorizedId uses Ref, simulate with a dictionaty
        post_method_resource.add_property_override(
                'AuthorizerId',
                {"Ref": api_gw_authorizer.logical_id})


app = cdk.App()
stk = Stk(app, "myStack")

app.synth()

Upvotes: 5

Sma Ma
Sma Ma

Reputation: 3715

Indeed. there is no example to do this via copy and paste ;). here is my example to create AWS cognito user pool and connect user pol authorizer with API gateway and lambda function using AWS CDK based on Java with Version 0.24.1.

This example ist just an example to provide an protected API for function called "Foo".

  • Cognito User Pool
  • API Gateway
  • Lambda
  • DynamoDB

    // -----------------------------------------------------------------------
    // Cognito User Pool
    // -----------------------------------------------------------------------
    CfnUserPool userPool = new CfnUserPool(this, "cognito",
        CfnUserPoolProps.builder()
            .withAdminCreateUserConfig(
                AdminCreateUserConfigProperty.builder()
                    .withAllowAdminCreateUserOnly(false)
                    .build())
            .withPolicies(
                PoliciesProperty.builder()
                    .withPasswordPolicy(
                        PasswordPolicyProperty.builder()
                            .withMinimumLength(6)
                            .withRequireLowercase(false)
                            .withRequireNumbers(false)
                            .withRequireSymbols(false)
                            .withRequireUppercase(false)
                            .build()
                    )
                    .build()
            )
            .withAutoVerifiedAttributes(Arrays.asList("email"))
            .withSchema(Arrays.asList(
                CfnUserPool.SchemaAttributeProperty.builder()
                    .withAttributeDataType("String")
                    .withName("email")
                    .withRequired(true)
                    .build()))
            .build());
    
    // -----------------------------------------------------------------------
    // Cognito User Pool Client
    // -----------------------------------------------------------------------
    new CfnUserPoolClient(this, "cognitoClient",
        CfnUserPoolClientProps.builder()
            .withClientName("UserPool")
            .withExplicitAuthFlows(Arrays.asList("ADMIN_NO_SRP_AUTH"))
            .withRefreshTokenValidity(90)
            .withUserPoolId(userPool.getRef())
            .build());
    
    // -----------------------------------------------------------------------
    // Lambda function
    // -----------------------------------------------------------------------
    Function function = new Function(this, "function.foo",
        FunctionProps.builder()
            // lamda code located in /functions/foo
            .withCode(Code.asset("functions/foo"))
            .withHandler("index.handler")
            .withRuntime(Runtime.NODE_J_S810)
            .build());
    
    // -----------------------------------------------------------------------
    // DynamoDB Table
    // -----------------------------------------------------------------------
    Table table = new Table(this, "dynamodb.foo", TableProps.builder()
        .withTableName("foo")
        .withPartitionKey(Attribute.builder()
            .withName("id")
            .withType(AttributeType.String)
            .build())
        .build());
    
    // GRANTS function -> table
    table.grantReadWriteData(function.getRole());
    
    // -----------------------------------------------------------------------
    // API Gateway
    // -----------------------------------------------------------------------
    
    // API Gateway REST API with lambda integration
    LambdaIntegration lambdaIntegration = new LambdaIntegration(function);
    RestApi restApi = new RestApi(this, "foo");
    
    // Authorizer configured with cognito user pool
    CfnAuthorizer authorizer = new CfnAuthorizer(this, "authorizer",
        CfnAuthorizerProps.builder()
            .withName("cognitoAuthorizer")
            .withRestApiId(restApi.getRestApiId())
            .withIdentitySource("method.request.header.Authorization")
            .withProviderArns(Arrays.asList(userPool.getUserPoolArn()))
            .withType("COGNITO_USER_POOLS")
            .build());
    
    // Bind authorizer to API ressource
    restApi.getRoot().addMethod("ANY", lambdaIntegration, MethodOptions
        .builder()
          .withAuthorizationType(AuthorizationType.Cognito)
          .withAuthorizerId(authorizer.getAuthorizerId())
        .build());
    

Upvotes: 4

Related Questions