AWS CDKでCognitoを使用してREST API 認証を実装してみた。

2022.02.17

概要

API Gateway REST API はリソースとメソッドのコレクションです。REST API は HTTPエンドポイント、Lambda関数、と他のAWSサービスと統合できます。REST API はリクエスト/レスポンスモデルを使用します。

IAMロール、Lambdaオーソライザー、Amazon Cognitoオーソライザーを使用してRESTAPIへのアクセスを制御できます。この記事では、Cognito ユーザープールをオーソライザーとして使用して REST API 認証を実装してみました。

 

 

やってみた

 

CDKアプリの作成

CDKをインストールする

  • 次のコマンドを使用してCDKをインストールしておきます。
npm install aws-cdk-lib

 

CDKアプリを作成する

  • 新しいディレクトリを作成しておきます。
  • CDKは、プロジェクトディレクトリの名前に基づいてソースファイルとクラスに名前を付けます。
#create new directory
mkdir api
cd api

 

  • cdk initコマンドを使用してアプリを初期化しておきます。
cdk init --language typescript

 

AWS サービスの作成

  • 新しいファイル [lib/index.ts] を作成して、作成する必要のあるAWSサービスを定義しておきます。
  • ファイルに次のAWSサービスを定義しておきます。
    • DynamoDBテーブル  :   パーティションキーを持つDynamoDBテーブル。
    • Lambda関数 : 環境変数とPythonランタイムを備えたLambda関数。
      • 環境変数にはテーブル名が含まれています。
      • コードは、DynamoDBにアクセスするための読み取りおよび書き込み権限をラムダに付与します。
    • API Gateway - GETメソッドのAPI。
    • Cognito User Pool : ドメインとクライアントを備えた User Pool。
    • Authorizer: APIを認証できるAuthorizerを定義しておきます。

 

import { Construct } from 'constructs';
import { LambdaIntegration, RestApi, Deployment, Stage, CfnAuthorizer, AuthorizationType} from 'aws-cdk-lib/aws-apigateway';
import { Table, AttributeType } from 'aws-cdk-lib/aws-dynamodb';
import { Runtime, Function, AssetCode } from 'aws-cdk-lib/aws-lambda';
import { RemovalPolicy, StackProps} from 'aws-cdk-lib';
import { UserPool } from 'aws-cdk-lib/aws-cognito'

export class apiStack extends Construct {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id);
    
    const dynamoTable = new Table(this, "items", {
       partitionKey: {
          name: "id",
          type: AttributeType.STRING,
        },
        tableName: "usersDB",
        removalPolicy: RemovalPolicy.DESTROY
    });
    
    const getAllLambda = new Function(this, "getAllItemsFunction", {
      code: new AssetCode("resources"),
      handler: "get-all.handler",
      runtime: Runtime.PYTHON_3_9,
      environment: {
        TABLE_NAME: dynamoTable.tableName,
      },
      functionName: "usersfunction",
    });
      
    dynamoTable.grantReadWriteData(getAllLambda); 
    const getAllIntegration = new LambdaIntegration(getAllLambda);
   
    // Create an API Gateway 
    const api = new RestApi(this, 'api', {
      restApiName: 'users-api',
      deploy: false
    });

    //create cognito user pool
    const userPool = new UserPool(this, 'userPool', {
      userPoolName: 'api-user-pool',
      standardAttributes: {
        email: {required: true, mutable: true},
      },
      removalPolicy: RemovalPolicy.DESTROY
    });

    //Add Domain to the user pool
    userPool.addDomain('domain', {
      cognitoDomain: {domainPrefix: 'api-users-domain'},
    });

    const userPoolClient = userPool.addClient('client', {
      userPoolClientName: 'api-client',
      preventUserExistenceErrors: true,
      authFlows: {
        adminUserPassword: true,
        userPassword: true
      }
    });

   //Define Authorizer
    const authorizer = new CfnAuthorizer(this, 'cfnAuth', {
      restApiId: api.restApiId,
      name: 'api-Authorizer',
      type: 'COGNITO_USER_POOLS',
      identitySource: 'method.request.header.Authorization',
      providerArns: [userPool.userPoolArn],
    })
    

    const items = api.root.addResource('users');
    items.addMethod('GET', getAllIntegration,{
      authorizationType: AuthorizationType.COGNITO,
      authorizer: {
        authorizerId: authorizer.ref
      }
    });

    const deployment = new Deployment(this, 'Deployment', {api});
    const stage = new Stage(this, 'dev_stage', {deployment, stageName : 'dev'});
    api.deploymentStage = stage;
  }
}

 

Lambda関数の作成

  • プロジェクトのメインディレクトリにresourcesディレクトリを作成しておきます。
mkdir resources

 

  • resourcesディレクトリに次のPythonファイルを作成しておきます。[resources/get-all.py]
  • 以下のコードは、DynamoDBテーブルのすべての項目を一覧表示します。

 

import json
import os
import boto3
from botocore.exceptions import ClientError

dynamodb = boto3.resource('dynamodb')
TABLE_NAME = os.environ['TABLE_NAME']

def handler(event, context):
    table = dynamodb.Table(TABLE_NAME)
    statusCode = 200
    body = "" 
    try:
        result = table.scan()
        body = json.dumps(result)
    except ClientError as e:
        statusCode = 404
        body = str(e)
    response = {
        'statusCode': statusCode,
        'body': body,
        'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
             }
    }
    return response

 

アプリにサービスを追加する

 

  • /lib/api_stack.ts ファイルに次のコードを追加しておきます。
#Import the Index file created in the previous step
import * as api from '../lib/index';
new api.apiStack(this, 'api');

 

CDK Deploy

  • Deploy する前に、環境をブートストラップする必要があります。
  • CDK Bootstrapは、AWS CDKがスタックをデプロイするために使用するS3バケットを作成します。
  • 次のコマンドを実行して、AWS環境をブートストラップしておきます。

 

cdk bootstrap

 

  • 次のコマンドを使用してCDKを展開しておきます。
cdk deploy

 

  • コンソールでは、サービスが作成されたことを見ることができます。

Cognito User Pool

 

API Gateway

 

Authorizer

 

DynamoDBテーブル

 

Lambda関数

 

API 認証とテストをする

  • APIに直接アクセスすると、Unauthorized エラーが発生します。APIにアクセスするにはトークンIDを提供する必要があります。
    curl 'https://id.execute-api.us-east-1.amazonaws.com/dev/users'
    
    //output
    {"message":"Unauthorized"}

     

  • 次のコマンドを使用してトークンを取得します。
//Command to get Token
aws cognito-idp admin-initiate-auth \
  --user-pool-id 'User Pool Id' \
  --client-id 'App Client Id' \
  --auth-flow "ADMIN_USER_PASSWORD_AUTH" \
  --auth-parameters USERNAME='user name',PASSWORD='password' \
  --region us-east-1

 

  • 前のコマンドの出力からのトークンIDを使用してAPIにアクセスしておきます。APIを使用してユーザーのリストを取得できるようになりました。
curl 'https://id.execute-api.us-east-1.amazonaws.com/dev/users'' \
--header 'Authorization: token-id'

 

まとめ

CDKでCognitoを使用してREST API 認証を実装してみました。

Reference :