API Gateway REST API + Cognito User Pool Authorizer + Lambda Functionな構成をAWS CDK v2で構築してみた

2022.04.07

こんにちは、CX事業本部 IoT事業部の若槻です。

前回に引き続き、今回もAWS CDK v2の肩慣らしをしていこうと思います。

今回は、API Gateway REST API + Cognito User Pool Authorizer + Lambda Functionな構成をAWS CDK v2で構築してみました。

環境

  • react@18.0.0
  • typescript@4.6.3

やってみた

このような構成を構築してみます。

実装

CDK Stackのコードは次のようになります。Lambda Functionコードの参照はReference project architectureを使用しています。

lib/aws-cdk-v2-app-stack.ts

import { Construct } from 'constructs';
import {
  Stack,
  StackProps,
  RemovalPolicy,
  aws_cognito,
  aws_lambda_nodejs,
  aws_apigateway,
} from 'aws-cdk-lib';

export interface AwsCdkV2AppStackProps extends StackProps {
  callbackUrls: string[];
  logoutUrls: string[];
  domainPrefix: string;
}

export class AwsCdkV2AppStack extends Stack {
  constructor(scope: Construct, id: string, props: AwsCdkV2AppStackProps) {
    super(scope, id, props);

    // User Pool
    const userPool = new aws_cognito.UserPool(this, 'userPool', {
      userPoolName: 'testUserPool',
      selfSignUpEnabled: false,
      standardAttributes: {
        email: { required: true, mutable: true },
        phoneNumber: { required: false },
      },
      signInCaseSensitive: false,
      autoVerify: { email: true },
      signInAliases: { email: true },
      accountRecovery: aws_cognito.AccountRecovery.EMAIL_ONLY,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // User Pool Domain
    userPool.addDomain('domain', {
      cognitoDomain: { domainPrefix: props.domainPrefix },
    }); //今回はログインUIを使わないので無くてもOK

    // User Pool Client
    userPool.addClient('client', {
      userPoolClientName: 'testUserPoolClient',
      generateSecret: false,
      oAuth: {
        callbackUrls: props.callbackUrls,
        logoutUrls: props.logoutUrls,
        flows: { authorizationCodeGrant: true },
        scopes: [
          aws_cognito.OAuthScope.EMAIL,
          aws_cognito.OAuthScope.OPENID,
          aws_cognito.OAuthScope.PROFILE,
        ],
      },
      authFlows: { adminUserPassword: true }, //cognitoIdp:adminInitiateAuth APIでユーザートークンを取得可能にする
    });

    // Lambda Function
    const lambdaFunc = new aws_lambda_nodejs.NodejsFunction(this, 'lambdaFunc');

    // Cognito Authorizer
    const cognitoAuthorizer = new aws_apigateway.CognitoUserPoolsAuthorizer(
      this,
      'cognitoAuthorizer',
      {
        authorizerName: 'CognitoAuthorizer',
        cognitoUserPools: [userPool],
      }
    );

    // Rest API
    const restApi = new aws_apigateway.RestApi(this, 'restApi');

    // Rest API Resource/Method
    restApi.root
      .addResource('data')
      .addMethod('GET', new aws_apigateway.LambdaIntegration(lambdaFunc), {
        authorizer: cognitoAuthorizer,
      });
  }
}

Lambda Functionのコードは次のようになります。固定のデータを200 OKで返すだけです。

lib/aws-cdk-v2-app-stack.lambdaFunc.ts

exports.handler = async () => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({ greeting: 'Hello from Lambda!' }),
  };
  return response;
};

cdk deployで構成をデプロイします。OutputでREST APIのエンドポイントのURI(https://{apiId}.execute-api.ap-northeast-1.amazonaws.com/prod/)が表示されるので控えます。

動作確認

先程控えたエンドポイントのURIを使用してdataリソースのURLを変数に指定します。

$ endpointUri=<endpointUri>
$ dataResourceUri=${endpointUri}data

作成されたUser Poolにユーザーを作成します。

ユーザーのID Token取得に必要な情報を変数に指定します。

$ userName=<userName>
$ userPassword=<userPassword>
$ userPoolId=<userPoolId>
$ clientId=<userPoolClientId>

cognitoIdp:adminInitiateAuth APIを使用してユーザーのID Tokenを取得します。

$ idToken=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${userPoolId} \
  --client-id ${clientId} \
  --auth-flow "ADMIN_USER_PASSWORD_AUTH" \
  --auth-parameters USERNAME=${userName},PASSWORD=${userPassword} \
  --query "AuthenticationResult.IdToken" \
  --output text)

リソースURIにID Tokenを使用してGETリクエストすると、認証が成功すればLambda Functionからデータが返されます。

$ curl -H "Authorization: ${idToken}" ${dataResourceUri}
{"greeting":"Hello from Lambda!"}

少しハマったところ

トークン違いによるUnauthorizedエラー

Authorizationヘッダーに間違えてAccess Tokenを指定して401 Unauthorizedエラーとなった。

正しくはID Tokenを指定します。

HTTP APIのHigh Level ConstructはまだExperimental

APIとしてaws_apigatewayv2(HTTP API)を使いたかったのですが、CDK v2のHigher level constructsがまだExperimentalでした。

なのでaws_apigateway(REST API)を今回は使いました。

おわりに

API Gateway REST API + Cognito User Pool Authorizer + Lambda Functionな構成をAWS CDK v2で構築してみました。

よくある構成だと思うので、今後よく使うテンプレートとなりそうです。ただHandler Fileの参照で使用したReference project architectureは少々挑戦的だったので、場合によってはEntryによる参照を使うかもしれません。

参考

以上