1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS CDKv2でCognito認証されたAPI Gatewayを構築して swaggeruiで疎通確認したメモ

Last updated at Posted at 2022-03-16

概要

AWS CDKでCognito認証されたAPI Gateway(HTTP API)を構築するをcdk v2で試してみた。

ソースコード(API)
ソースコード(Swagger)

環境

  • windows10
  • docker-desktop(Docker version 20.10.13, build a224086)
  • cdk 2.16.0

CDK構築

package.json
cdk/package.json
{
  "name": "cdk",
  "version": "0.1.0",
  "bin": {
    "cdk": "bin/cdk.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "lambda-with-cognito-deploy": "cdk -a \"npx ts-node --prefer-ts-exts bin/lambdaWithCognito.ts\" deploy LambdaWithCognitoStack -c @aws-cdk/core:newStyleStackSynthesis=true"
  },
  "devDependencies": {
    "@types/fs-extra": "^9.0.13",
    "@types/jest": "^27.4.1",
    "@types/node": "17.0.21",
    "aws-cdk": "2.16.0",
    "fs-extra": "^10.0.1",
    "jest": "^27.5.1",
    "ts-jest": "^27.1.3",
    "ts-node": "^10.7.0",
    "typescript": "~4.6.2"
  },
  "dependencies": {
    "@aws-cdk/aws-apigatewayv2-alpha": "^2.16.0-alpha.0",
    "@aws-cdk/aws-apigatewayv2-authorizers-alpha": "^2.16.0-alpha.0",
    "@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.16.0-alpha.0",
    "aws-cdk-lib": "2.16.0",
    "constructs": "^10.0.89",
    "dotenv": "^16.0.0",
    "eslint": "^8.11.0",
    "source-map-support": "^0.5.21"
  }
}
cdk/.env
PROJECT_ID=hogefuga
DOMAIN_PREFIX=piyo
CALLBACK_URLS=http://localhost:3200/oauth2-redirect.html
LOGOUT_URLS=http://localhost:3200/oauth2-redirect.html
FRONTEND_URLS=http://localhost:3200
cdk/lib/LambdaWithCognitoStack.ts
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as cognito from 'aws-cdk-lib/aws-cognito'
import * as apigw from '@aws-cdk/aws-apigatewayv2-alpha'
import * as intg from '@aws-cdk/aws-apigatewayv2-integrations-alpha'
import * as authz from '@aws-cdk/aws-apigatewayv2-authorizers-alpha'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
import { CfnOutput, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'
import { Construct } from 'constructs'

interface Props extends StackProps {
  projectId: string
  domainPrefix: string
  frontendUrls: string[]
  callbackUrls: string[]
  logoutUrls: string[]
}
export class LambdaWithCognitoStack extends Stack {
  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props)

    const userPool = new cognito.UserPool(this, `${props.projectId}-userPool`, {
      selfSignUpEnabled: false,
      standardAttributes: {
        email: { required: true, mutable: true },
        phoneNumber: { required: false },
      },
      signInCaseSensitive: true,
      autoVerify: { email: true },
      signInAliases: { email: true },
      accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
      removalPolicy: RemovalPolicy.DESTROY,
    })
    userPool.addDomain(`${props.projectId}-userPool-domain`, {
      cognitoDomain: { domainPrefix: props.domainPrefix },
    })
    const userPoolClient = userPool.addClient('client', {
      oAuth: {
        scopes: [
          cognito.OAuthScope.EMAIL,
          cognito.OAuthScope.OPENID,
          cognito.OAuthScope.PROFILE,
        ],

        callbackUrls: props.callbackUrls,
        logoutUrls: props.logoutUrls,
        flows: { authorizationCodeGrant: true },
      },
    })
    const authorizer = new authz.HttpUserPoolAuthorizer(
      `${props.projectId}-CognitoAuthorizer`,
      userPool,
      {
        authorizerName: 'CognitoAuthorizer',
        userPoolClients: [userPoolClient],
      },
    )

    const handler = new NodejsFunction(
      this,
      `${props.projectId}-scenario-lambda`,
      {
        runtime: lambda.Runtime.NODEJS_14_X,
        entry: '../src/handler/api/getScenario.ts',
        functionName: 'scenario',
        description: 'シナリオ',
      },
    )

    const httpApi = new apigw.HttpApi(this, `${props.projectId}-apigw`, {
      createDefaultStage: false,
      corsPreflight: {
        allowOrigins: props.frontendUrls,
        allowMethods: [apigw.CorsHttpMethod.ANY],
        allowHeaders: ['authorization'],
      },
    })

    httpApi.addRoutes({
      methods: [apigw.HttpMethod.GET],
      path: '/scenario',
      integration: new intg.HttpLambdaIntegration(
        `${props.projectId}-scenarioIntegration`,
        handler,
      ),
      authorizer,
    })
    const stage = new apigw.HttpStage(this, `${props.projectId}-apistage`, {
      httpApi,
      stageName: 'api',
      autoDeploy: true,
    })

    new CfnOutput(this, 'OutputApiUrl', { value: stage.url })
    new CfnOutput(this, 'OutputDomainPrefix', { value: props.domainPrefix })
    new CfnOutput(this, 'OutputClientId', {
      value: userPoolClient.userPoolClientId,
    })
  }
}
cdk/bin/lambdaWithCognito.ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { LambdaWithCognitoStack } from '../lib/LambdaWithCognitoStack'
import * as dotenv from 'dotenv'

dotenv.config()
const envList = [
  'PROJECT_ID',
  'DOMAIN_PREFIX',
  'CALLBACK_URLS',
  'LOGOUT_URLS',
  'FRONTEND_URLS',
] as const
for (const key of envList) {
  if (!process.env[key]) throw new Error(`please add ${key} to .env`)
}
const processEnv = process.env as Record<typeof envList[number], string>

const app = new cdk.App()
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
}
const projectId = processEnv.PROJECT_ID

new LambdaWithCognitoStack(app, `${projectId}-stack`, {
  projectId,
  domainPrefix: processEnv.DOMAIN_PREFIX,
  callbackUrls: processEnv.CALLBACK_URLS.split(','),
  logoutUrls: processEnv.LOGOUT_URLS.split(','),
  frontendUrls: processEnv.FRONTEND_URLS.split(','),
  env
})

デプロイ後に表示されるURL、clientID、OutputDomainPrefixは次のSwaggerの環境変数に使うため控えておく。

SWaggerUIでの確認

SwaggerUIが現在(2022/03/17)openapi3.0.3までしか対応していないので、3.1で書いたファイルを一つにまとめた後、versionを3.1から3.0に書き換えるスクリプトを追記した。

redocを使ってもいいかもしれない。

openapi

documents/api-schema/package.json
{
  "name": "app",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "redoc": "redoc-cli bundle ./openapi.yml --options.menuToggle --options.pathInMiddlePanel  -o ./dist/index.html",
    "preswaggerui": "openapi bundle openapi.yml --output swagger.yaml && bash replace.sh",
    "swaggerui": "bash ./bin/swagger.sh"
  },
  "keywords": [],
  "author": "hibohiboo <hibohiboo66+github@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "@redocly/openapi-cli": "^1.0.0-beta.87",
    "redoc": "^2.0.0-rc.65",
    "redoc-cli": "^0.13.9"
  }
}
documents/api-schema/.env.sample
OUTPUT_API_URL=https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/api
OUTPUT_DOMAIN_PREFIX=piyo
documents/api-schema/replace.sh
#!/bin/bash

. .env

sed -i -e "s/openapi: 3.1.0/openapi: 3.0.3/" swagger.yaml
sed -i -e "s|{{OutputApiUrl}}|$OUTPUT_API_URL|" swagger.yaml
sed -i -e "s|{{OutputDomainPrefix}}|$OUTPUT_DOMAIN_PREFIX|g" swagger.yaml
documents/api-schema/openapi.yml
openapi: 3.1.0
info:
  title: hoge
  description: fugaAPI
  version: 0.0.1
servers:
  - url: "{{OutputApiUrl}}"
paths:
  /scenario:
    $ref: ./paths/cartagraph.yml

components:
  securitySchemes:
    OAuth2:
      $ref: ./components/schemas/oauth2.yml
documents/api-schema/components/schemas/oauth2.yml
type: oauth2
description: For more information, see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html
flows:
  authorizationCode:
    authorizationUrl: https://{{OutputDomainPrefix}}.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize
    tokenUrl: https://{{OutputDomainPrefix}}.auth.ap-northeast-1.amazoncognito.com/oauth2/token
    scopes:
      openid: openid token
documents/api-schema/components/schemas/oauth2.yml
type: oauth2
description: For more information, see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html
flows:
  authorizationCode:
    authorizationUrl: https://{{OutputDomainPrefix}}.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize
    tokenUrl: https://{{OutputDomainPrefix}}.auth.ap-northeast-1.amazoncognito.com/oauth2/token
    scopes:
      openid: openid token
documents/api-schema/paths/cartagraph.yml
get:
  operationId: scenarios
  security:
    - OAuth2: [ openid, token ]
  responses:
    200:
      description: 200 OK
      content:
        application/json:
          schema:
            required:
              - scenarios
            properties:
              scenarios:
                type: array
                items:
                  required:
                    - name
                  properties:
                    name:
                      type: string
            example:
              scenarios:
                - name: hina
                - name: koharu
                - name: konatsu

swaggerui

documents/api-schema/docker/docker-compose.yml
version: '3.8'

services:
  swagger:
    image: swaggerapi/swagger-ui:latest
    environment:
      API_URL: /swagger.yaml
      BASE_URL: /
    env_file: .env
    volumes:
      - ../swagger.yaml:/usr/share/nginx/html/swagger.yaml
      - ../components:/usr/share/nginx/html/components
      - ../paths:/usr/share/nginx/html/paths
    ports:
      - 3200:8080
documents/api-schema/docker/.env
OAUTH_CLIENT_ID=発行されたcliendID
DOMAIN_PREFIX=piyo

cognitoの設定

デプロイされたらコンソールにログインし、cognitoの画面から検証用のユーザを作成する。

image.png

動作確認

dockerを起動し、 http://localhost:3200/に移動。

APIの認証有効確認

ただ叩いて401が帰ってくれば正常
image.png
image.png
401であることを確認

image.png

認証

image.png

client secretには何も入れずにAuthorizeをクリック

image.png

image.png

メールアドレスとパスワードを入力し、認証に成功すると元の画面が以下の表示となる。

image.png

再度executeとして結果が変化することを確認。

image.png

遭遇したエラー

認証画面に飛ぼうとすると以下のエラー。

An error was encountered with the requested page.

原因はコールバックURLの設定ミス。

# エラーとなるコード
CALLBACK_URLS=http://localhost:3200/
# 成功するコード
CALLBACK_URLS=http://localhost:3200/oauth2-redirect.html

参考

AmplifyでCognitoのHosted UIを利用した認証を最低限の実装で動かしてみて動作を理解する
AWS SAMでCognito UserPoolsの認証付きAPIを作成する
cdk
Amazon Cognito ユーザープールをオーソライザーとして使用して REST API へのアクセスを制御する
Amplify auth は複数のコールバックURLに対応していない
OPENAPI
redocly

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?