1
0

More than 1 year has passed since last update.

API Gateway の HTTP API を AWS CDK で作成する

Posted at

CDKを使用してAPI GatewayのHTTP APIを作成するサンプルです。
認証はCognitoを使用して認可コードグラントを利用します。

AWS CDK

% npm install @aws-cdk/aws-apigatewayv2-alpha @aws-cdk/aws-apigatewayv2-authorizers-alpha @aws-cdk/aws-apigatewayv2-integrations-alpha

cognitoドメインのdomainPrefixはユニークな文字列である必要があります。cognitoドメインの代わりにカスタムドメインを使用することも可能です。

bin/sample.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { SampleStack } from '../lib/sample-stack';

const app = new cdk.App();
const contextKey = process.env.ENV === 'prod' ? 'prod' : 'dev';
const context = app.node.tryGetContext(contextKey);

new SampleStack(app, 'SampleStack', { context });
lib/sample-stack.ts
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodeJs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as apigw from '@aws-cdk/aws-apigatewayv2-alpha';
import * as authz from '@aws-cdk/aws-apigatewayv2-authorizers-alpha';
import * as intg from '@aws-cdk/aws-apigatewayv2-integrations-alpha';

export type SampleStackProps = cdk.StackProps & { context: any };

export class SampleStack extends cdk.Stack {
    private context: any;

    constructor(scope: Construct, id: string, props: SampleStackProps) {
        super(scope, id, props);
        this.context = props.context;

        // Cognito User Pool & User Pool Client
        const userPool = this.createUserPool(this.context.cognito.domainPrefix);
        const userPoolClient = this.createUserPoolClient(userPool, this.context.cognito.callbackUrls);

        // Authorizer
        const authorizer = this.createAuthorizer(userPool, userPoolClient);

        // Lambda
        const func = this.createFunc();

        // API Gateway
        this.createApiGateway(func, authorizer);
    }

    /**
     * Cognito ユーザープールの作成
     * {@link UserPool | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.UserPool.html}
     * {@link UserPoolDomainOptions | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.UserPoolDomainOptions.html}
     */
    private createUserPool(cognitoDomainPrefix: string): cognito.IUserPool {
        const userPool = new cognito.UserPool(this, 'UserPool', {
            selfSignUpEnabled: false,
            standardAttributes: {
                email: { required: true, mutable: true },
                phoneNumber: { required: false },
            },
            autoVerify: { email: true },
            signInAliases: { email: true },
            accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
            removalPolicy: cdk.RemovalPolicy.DESTROY,
        });

        userPool.addDomain('UserPoolDomain', {
            // cognitoドメインまたはカスタムドメインが選択可能
            cognitoDomain: { domainPrefix: cognitoDomainPrefix },
            // customDomain: {}
        });
        return userPool;
    }

    /**
     * Cognito ユーザープールクライアントの作成
     * {@link UserPoolClient | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.UserPoolClient.html}
     */
    private createUserPoolClient(userPool: cognito.IUserPool, callbackUrls: string[]): cognito.IUserPoolClient {
        return userPool.addClient('client', {
            generateSecret: true,
            oAuth: {
                callbackUrls,
                scopes: [cognito.OAuthScope.OPENID],
                flows: {
                    authorizationCodeGrant: true,
                    clientCredentials: false,
                    implicitCodeGrant: false,
                },
            },
            authFlows: {
                adminUserPassword: false,
                custom: false,
                userPassword: false,
                userSrp: false,
            },
        });
    }

    /**
     * @description API Gateway用オーソライザーの作成
     * {@link class HttpUserPoolAuthorizer | https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_aws-apigatewayv2-authorizers-alpha.HttpUserPoolAuthorizer.html}
     */
    private createAuthorizer(
        userPool: cognito.IUserPool,
        userPoolClient: cognito.IUserPoolClient
    ): apigw.IHttpRouteAuthorizer {
        return new authz.HttpUserPoolAuthorizer('Authorizer', userPool, {
            userPoolClients: [userPoolClient],
        });
    }

    /**
     * @description Lambdaの作成
     * {@link ParamsAndSecretsLayerVersion | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.ParamsAndSecretsLayerVersion.html}
     * {@link NodejsFunction | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html}
     */
    private createFunc(): lambdaNodeJs.NodejsFunction {
        return new lambdaNodeJs.NodejsFunction(this, 'SampleFunc', {
            entry: 'src/lambda/sample/index.ts',
            handler: 'handler',
            runtime: lambda.Runtime.NODEJS_18_X,
            timeout: cdk.Duration.minutes(5),
        });
    }

    /**
     * @description ラムダの作成
     * {@link class HttpApi | https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_aws-apigatewayv2-alpha.HttpApi.html}
     */
    private createApiGateway(
        func: lambdaNodeJs.NodejsFunction,
        authorizer: apigw.IHttpRouteAuthorizer
    ): apigw.IHttpApi {
        const httpApi = new apigw.HttpApi(this, 'SampleHttpApi');
        const integration = new intg.HttpLambdaIntegration('Integration', func);
        httpApi.addRoutes({
            methods: [apigw.HttpMethod.GET],
            path: '/sample',
            authorizer,
            integration,
        });
        return httpApi;
    }
}
src/lambda/sample/index.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
    return {
        statusCode: 200,
        body: '',
    };
}
cdk.json
{
    // 略
    "context": {
        // 略
        "dev": {
            "cognito": {
                "domainPrefix": "taaazyyy-sample-230818",
                "callbackUrls": ["https://example.com/callback"]
            }
        },
        "prod": {
            "cognito": {
                "domainPrefix": "taaazyyy-sample-230818",
                "callbackUrls": ["https://example.com/callback"]
            }
        }
    }
}

Postmanで検証

認可コードグラントを使用した時のPostmanを使った検証例です。
あらかじめユーザープールにユーザーを作成しておく必要があります。

PostmanのHTTPリクエスト作成ページでのAuthorizationタブで下記のように設定します。

  • Grant Type: Authorization Code
  • Callback URL: ※ユーザープールクライアント作成時に指定したコールバックURLのいずれか
  • Auth URL: https://{Cognitoドメイン}/oauth2/authorize
  • Access Token URL: https://{Cognitoドメイン}/oauth2/token
  • Client ID: Cognitoのアプリケーションクライアントに表示されているクライアントID
  • Client Secret: Cognitoのアプリケーションクライアントに表示されているクライアントシークレット
  • Scope: openid

Postman Authorization

「Get New Access Token」ボタンをクリックするとログイン画面が表示されるので、ログインします。
認証に成功したらAPI Gatewayのエンドポイントを入力してSendボタンをクリックして検証を行うことができます。

Cognito Login

テスト

test/sample.test.ts
test/sample.test.ts
import * as cdk from 'aws-cdk-lib';
import { Template, Match } from 'aws-cdk-lib/assertions';
import { SampleStack } from '../lib/sample-stack';

const context = {
    cognito: {
        domainPrefix: 'taaazyyy-sample-test',
        callbackUrls: ['https://example.com/callback'],
    },
};

test('UserPool', () => {
    const app = new cdk.App();
    const stack = new SampleStack(app, 'SampleStack', { context });
    const template = Template.fromStack(stack);

    // User Pool
    template.resourceCountIs('AWS::Cognito::UserPool', 1);
    template.hasResourceProperties('AWS::Cognito::UserPool', {
        Schema: [
            { Mutable: true, Name: 'email', Required: true },
            {
                Mutable: true,
                Name: 'phone_number',
                Required: false,
            },
        ],
        AutoVerifiedAttributes: ['email'],
        UsernameAttributes: ['email'],
        AccountRecoverySetting: {
            RecoveryMechanisms: [{ Name: 'verified_email', Priority: 1 }],
        },
    });

    // User Pool Domain
    template.resourceCountIs('AWS::Cognito::UserPoolDomain', 1);
    template.hasResourceProperties('AWS::Cognito::UserPoolDomain', {
        Domain: 'taaazyyy-sample-test',
        UserPoolId: Match.anyValue(),
    });

    // User Pool Client
    template.resourceCountIs('AWS::Cognito::UserPoolClient', 1);
    template.hasResourceProperties('AWS::Cognito::UserPoolClient', {
        GenerateSecret: true,
        CallbackURLs: ['https://example.com/callback'],
        AllowedOAuthScopes: ['openid'],
        AllowedOAuthFlows: ['code'],
        AllowedOAuthFlowsUserPoolClient: true,
        ExplicitAuthFlows: ['ALLOW_REFRESH_TOKEN_AUTH'],
    });
});

test('Lambda', () => {
    const app = new cdk.App();
    const stack = new SampleStack(app, 'SampleStack', { context });
    const template = Template.fromStack(stack);

    // Lambda
    template.resourceCountIs('AWS::Lambda::Function', 1);
    template.hasResourceProperties('AWS::Lambda::Function', {
        Runtime: 'nodejs18.x',
        Timeout: 300,
    });
});

test('ApiGateway', () => {
    const app = new cdk.App();
    const stack = new SampleStack(app, 'SampleStack', { context });
    const template = Template.fromStack(stack);

    // API Gateway
    template.resourceCountIs('AWS::ApiGatewayV2::Api', 1);
    template.hasResourceProperties('AWS::ApiGatewayV2::Api', {
        Name: 'SampleHttpApi',
        ProtocolType: 'HTTP',
    });

    // Route
    template.resourceCountIs('AWS::ApiGatewayV2::Route', 1);
    template.hasResourceProperties('AWS::ApiGatewayV2::Route', {
        RouteKey: 'GET /sample',
    });

    // Authorizer
    template.resourceCountIs('AWS::ApiGatewayV2::Authorizer', 1);
    template.hasResourceProperties('AWS::ApiGatewayV2::Authorizer', {
        AuthorizerType: 'JWT',
    });
});

エラー

ERESOLVE unable to resolve dependency tree

aws-cdk-libと@aws-cdk/xxxxでバージョンが異なるとnpm installで下記のエラーが発生することがあります。
インストールし直すなどバージョンを揃えることで解消できます。

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: sample@0.1.0
npm ERR! Found: aws-cdk-lib@2.91.0
npm ERR! node_modules/aws-cdk-lib
npm ERR!   aws-cdk-lib@"2.91.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer aws-cdk-lib@"2.92.0" from @aws-cdk/aws-apigatewayv2-alpha@2.92.0-alpha.0
npm ERR! node_modules/@aws-cdk/aws-apigatewayv2-alpha
npm ERR!   @aws-cdk/aws-apigatewayv2-alpha@"*" from the root project

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0