AWS CDKでCognitoを使ったREST APIを作ってみる
Cognitoを認証基盤としたREST APIをCDKで作成する機会があったので、
個人の記録として整理しておきたいと思います。
実装する構成は以下です。
CDK準備
CDKのバージョンはv2です。環境としてはcdk bootstrap済みであることを想定しています。
$ mkdir cognito && cd cognito
$ cdk init app --language typescript
AWS CDK (Typescript)
/lib/cognito-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as logs from 'aws-cdk-lib/aws-logs';
export class CognitoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Cognitoユーザープールの作成
const userPool = new cognito.UserPool(this, 'UserPool', {
selfSignUpEnabled: true, // ユーザーが自己登録できるように設定
userVerification: {
emailSubject: 'Verify your email for our app!', // メールの検証メッセージ
emailBody: 'Thanks for signing up to our app! Your verification code is {####}', // メール本文
emailStyle: cognito.VerificationEmailStyle.CODE, // 検証コードを含むメール
},
signInAliases: {
email: true, // メールアドレスをサインインに使用
},
});
// Cognitoユーザープールのクライアントを作成
const userPoolClient = userPool.addClient('UserPoolClient', {
userPoolClientName: 'sample_client', //アプリケーションクライアント名
authFlows: {
adminUserPassword: true,
userPassword: true,
},
});
// カスタムドメインを追加
userPool.addDomain('UserPoolDomain', {
cognitoDomain: {
domainPrefix: 'testsampledomain',
}
});
// DynamoDBテーブルの作成
const table = new dynamodb.Table(this, 'DynamoDB', {
tableName: 'sample_table', // テーブル名
partitionKey: {
name: 'id',
type: dynamodb.AttributeType.NUMBER,
},
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// APIGW用のロググループ
const accessLogGroup = new logs.LogGroup(this, 'AccessLog',{
logGroupName: 'APIGateway-AccessLog',
retention: logs.RetentionDays.ONE_DAY,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// API Gatewayの作成
const api = new apigateway.RestApi(this, 'ApiGateway', {
restApiName: 'sample_API',
deployOptions: {
stageName: 'prod', // ステージ名を設定
tracingEnabled: true, // X-Ray
dataTraceEnabled: true,
loggingLevel: apigateway.MethodLoggingLevel.ERROR,
accessLogDestination: new apigateway.LogGroupLogDestination(accessLogGroup),
accessLogFormat: apigateway.AccessLogFormat.clf()
},
});
// Lambda関数の作成
const lambdaFunction = new lambda.Function(this, 'Lambda', {
runtime: lambda.Runtime.PYTHON_3_9,
handler: 'sample.handler', // Lambda関数のエントリーポイント
code: lambda.Code.fromAsset('lambda'), // Lambda関数のコードを 'lambda' フォルダに配置
tracing: lambda.Tracing.ACTIVE,
timeout: cdk.Duration.seconds(30),
environment: {
TABLE_NAME: table.tableName,
},
});
// Lambda関数に必要なIAMロールを作成
const lambdaRole = new iam.Role(this, 'LambdaRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});
// DynamoDBテーブルへのアクセスポリシーをLambda関数に追加
table.grantReadData(lambdaFunction);
// Lambda関数にAPI Gatewayの呼び出しを許可
lambdaFunction.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com'));
// API GatewayのルートリソースにLambda統合を作成
const integration = new apigateway.LambdaIntegration(lambdaFunction);
// Cognitoユーザープール用のAuthorizerを作成
const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
cognitoUserPools: [userPool],
});
// API GatewayメソッドにCognito認証を適用
const method = api.root.addMethod('GET', integration, {
authorizationType: apigateway.AuthorizationType.COGNITO,
authorizer,
});
}
}
Lambdaコード(Python)
/lambda/sample.py
Pythonで用意しました。lambdaフォルダを作成し、sample.pyとして格納します。
動作としては、API Gatewayアクセスで発火し、DynamoDBテーブルから「id」と「userName」を取得してきます。
import os
import boto3
def handler(event, context):
# DynamoDBテーブル名を環境変数から取得
table_name = os.environ['TABLE_NAME']
# クエリパラメータからidを取得
id = event['queryStringParameters']['id']
# DynamoDBクライアントの初期化
dynamodb = boto3.resource('dynamodb')
# DynamoDBテーブルの取得
table = dynamodb.Table(table_name)
# テーブルから指定されたidのデータを取得
response = table.get_item(Key={'id': int(id)})
# レスポンスデータの作成
if 'Item' in response:
item = response['Item']
user_name = item['userName']
response_data = {
'statusCode': 200,
'body': f'ID: {id}, User Name: {user_name}'
}
else:
response_data = {
'statusCode': 404,
'body': f'ID: {id} not found'
}
return response_data
CDKデプロイ
$ cdk synth
$ cdk deploy
これでインフラが出来上がりました。
DynamoDBにデータ登録
DynamoDBにデータを登録します。
ここからは、マネージメントコンソールでポチポチ作業していきます。
テーブルにはidとuserNameを登録します。以下がイメージです。
1.AWS コンソールにログインし、DynamoDBを選択します。
2.左メニューのテーブル → 「項目を探索」を選択します。
3.テーブル一覧から「sample_table」を選択します。
4.「項目を作成」をクリックします。
5.設定値を入力します。
「id」:数字
「userName」:文字列
6.「項目を作成」をクリックします。
7.項番の4~6を何回か繰り返し、適宜項目を追加します。
以上でDynamoDBへのデータ登録は完了です。
Cognitoユーザプールにユーザ作成
出来上がったCognitoユーザプール内にユーザを作成します。
トークンを取得するためのユーザとなります。
1.AWS コンソールにログインし、Cognitoを選択します。
2.左メニューのユーザープールをクリックします。
3.ユーザープール一覧からCDKで作成したユーザープールをクリックします。
4.「アプリケーションの統合」タブをクリックします。
5.最下段までスクロールし、アプリケーションクライアント名をクリックします。
6.「ホストされたUIを表示」をクリックします。
7.Sign in画面が別タブで表示されますので、下部の「Sign up」をクリックします。
8.Sign up画面に遷移します。メールアドレスとパスワードを入力しユーザ登録します。メールアドレスは受信可能なアドレスにしてください。後続手順に必要な認証コードがメールで届きます。
以下のようなメールが届きますので認証コードをコピーします。
9.認証コードの登録画面に遷移しますので、受信したメールに記載されているコードをペーストするか入力します。
「Confirm account」をクリックすれば登録完了です。
10.ユーザが無事登録できたかをCognito画面で確認します。
Cognitoの画面に戻り、「ユーザー」タブをクリックします。
11.ユーザー一覧画面にユーザー名が登録されているか確認します。
確認ステータス欄が「確認済み」となっていれば完了です。
APIへアクセス
ここからはAPIへアクセスするための手順となります。
API Gatewayにアクセスするためには、CognitoのIdトークンが必要です。
そのためまずは、CognitoにアクセスしIdトークンを取得します。
AWS CLIコマンドを使用しますので、AWS CLIが実行できる環境が必要になります。
Idトークンの取得
トークンを取得するには、以下4つの情報が必要です。
・user-pool-id (ユーザープールID)
・client-id (クライアントID)
・USERNAME (ユーザー名)
・PASSWORD (パスワード)
・user-pool-id (ユーザープールID)
ユーザープール画面で確認ができます。
・client-id (クライアントID)
アプリケーションの統合タブ → 最下段 → クライアントIDが確認できます
・USERNAME (ユーザー名)
ユーザータブ → ユーザー一覧画面から確認ができます。
・PASSWORD (パスワード)
ユーザー登録時に設定したパスワードです。
4つの情報が全てそろったら、以下のAWS CLIコマンドを実行します。
aws cognito-idp admin-initiate-auth \
--user-pool-id XXXXXXXXXXXX \
--client-id XXXXXXXXXXXXXXXXXXXXXX \
--auth-flow "ADMIN_USER_PASSWORD_AUTH" \
--auth-parameters \
USERNAME=XXXXXXXXXXXXXXXXXXXXXX,PASSWORD=XXXXXXXXX
コマンドが成功すると以下のように応答が返ってきます。
返ってくるトークンは3つ(AccessToken、RefreshToken、IdToken)ですが、使うのはIdTokenなので、IdTokenをコピーします。
{
"ChallengeParameters": {},
"AuthenticationResult": {
"AccessToken": "XXXXXXXXXXXXXXXXXXXXXXXXX",
"ExpiresIn": 3600,
"TokenType": "Bearer",
"RefreshToken": "XXXXXXXXXXXXXXXXXXXXXXXXX",
"IdToken": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
これでAPI Gatewayにアクセスするためのトークンが取得できました。
curlを実行する場合
curlコマンドを利用してAPI Gatewayにアクセスします。
以下のcurlコマンドでid:1のデータを取得します。<Idトークン>の箇所に、取得したIDトークンをペーストします。
$ curl "https://XXXX.execute-api.ap-northeast-1.amazonaws.com/prod/?id=1" \
--header 'Authorization: <Idトークン>'
以下が実行結果です。
無事取得できましたね。
ID: 1, User Name: sato
ちなみにトークンを入れずにアクセスを行うと・・・
未認証ということで怒られてしまいます。当然ですが。
$ curl "https://XXXX.execute-api.ap-northeast-1.amazonaws.com/prod/?id=1"
{"message":"Unauthorized"}
ブラウザで実行する場合
ブラウザを使ってAPIGatewayにアクセスするには、ModHeaderを使うのが楽です。
取得したトークンをブラウザ上で埋め込み、API Gatewayにアクセスしてみます。
※筆者はchromeの拡張機能として利用しています。
Name:Authorization
Value:取得したIdトークン
API Gatewayにアクセス
ModHeaderにトークンをセットできたら、API GatewayのURL(GETメソッド)宛にアクセスを行います。以下はid:1のデータを取得します。
https://XXXX.execute-api.ap-northeast-1.amazonaws.com/prod/?id=1
無事取得できましたね。
末尾のid番号を変えると、別ID情報を取得することができます。
手順としては以上となります。
まとめ
ここまで読んでいただきありがとうございます。
今回はCDKを使ってCognitoを認証基盤としたREST APIを実装してみました。少しでも誰かの役に立てれば幸いです。