情報システム室 進地 です。
ReactからCognitoオーソライザーを設定したAPI Gateway + Lambdaをコールしてみました。 XHRのライブラリにはaxiosを使っています。
Backend
AWS CDKコードサンプル
lib/someStack.ts
import { aws_lambda, Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha';
import { ManagedPolicy, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
import {
Cors,
EndpointType,
LambdaIntegration,
MethodLoggingLevel,
RestApi,
CognitoUserPoolsAuthorizer
} from "aws-cdk-lib/aws-apigateway";
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as dotenv from 'dotenv';
dotenv.config();
export class SomeStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
const lambdaRole = new Role(this, "SomeLambdaRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromManagedPolicyArn(
this,
"lambda",
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
)
],
description: "Basic Lambda Role"
});
const some = new PythonFunction(this, 'SomeHandler', {
functionName: 'SomeHandler',
runtime: aws_lambda.Runtime.PYTHON_3_11,
entry: 'lambda/some',
handler: 'lambda_handler',
role: lambdaRole,
timeout: Duration.seconds(30)
});
const someIntegration = new LambdaIntegration(some);
const apigw = new RestApi(this, "apigw", {
restApiName: "apigw",
deployOptions: {
loggingLevel: MethodLoggingLevel.INFO,
dataTraceEnabled: true,
metricsEnabled: true,
},
defaultCorsPreflightOptions: {
allowOrigins: Cors.ALL_ORIGINS,
allowMethods: Cors.ALL_METHODS,
allowHeaders: Cors.DEFAULT_HEADERS,
statusCode: 200,
},
endpointTypes: [EndpointType.REGIONAL],
cloudWatchRole: true
});
// 既存のユーザープールを使う
const userPool = cognito.UserPool.fromUserPoolArn(
this,
'ExistingUserPool',
`arn:aws:cognito-idp:${process.env.AWS_REGION}:${process.env.AWS_ACCOUNT_ID}:userpool/${process.env.COGNITO_USER_POOL_ID}`
);
// Cognitoのユーザープールにクライアントを追加
const userPoolClientName = 'SomeUserPoolClient';
userPool.addClient(userPoolClientName, {
userPoolClientName: userPoolClientName,
// パスワード認証を有効にするためには下記の設定が必要
authFlows: { userPassword: true },
});
// CognitoのAuthorizerの作成
const cognitoAuthorizer = new CognitoUserPoolsAuthorizer(
this,
'cognitoAuthorizer',
{
cognitoUserPools: [userPool],
}
);
const someEndpoint = apigw.root.addResource("some");
someEndpoint.addMethod(
"POST",
someIntegration,
{
authorizer: cognitoAuthorizer
}
);
}
}
LambdaはPythonを使っています。
Cognitoユーザプールクライアントを作成する際に、パスワード認証を有効にするためにauthFlows: { userPassword: true }
を設定しています(ハイライト部)。
.envファイルは下記のように設定しています。
.env
COGNITO_USER_POOL_ID=<接続するCognitoユーザプールIDを設定する>
AWS_ACCOUNT_ID=<AWSアカウントIDを設定する>
AWS_REGION=ap-northeast-1
Lambdaコードサンプル
someParameter
というキーで受け取ったパラメータをそのまま返すLambdaとします。
lambda/some/index.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
import json
# ロギング用
logging.basicConfig(format='%(asctime)s - %(threadName)s - %(module)s:%(funcName)s(%(lineno)d) - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
def lambda_handler(
event: dict,
context: dict
) -> dict:
"""
実質メイン処理
"""
logger.info("処理開始 [__PROGRESS__]")
param = json.loads(event["body"])
statusCode = 200
body = {
"someParameter": param.get("someParameter")
}
logger.info("処理終了 [__PROGRESS__]")
return {
"statusCode": statusCode,
"headers": {"Access-Control-Allow-Origin": "*"},
"body": json.dumps(body)
}
if __name__ == "__main__":
event = {
}
lambda_handler(event, {})
Frontend
Reactコードサンプル
src/App.js
/* Cognitoオーソライザーを設定したAPI Gateway + Lambdaをコールする */
const callAPI = async () => {
try {
/* トークンを取得 */
const token = await getCognitoAccessToken(
process.env.REACT_APP_COGNITO_URL,
'USER_PASSWORD_AUTH',
process.env.REACT_APP_COGNITO_USER_POOL_CLIENT_ID,
process.env.REACT_APP_COGNITO_USERNAME,
process.env.REACT_APP_COGNITO_PASSWORD
);
/* APIに渡したいパラメータを設定 */
const requestPayload = {
'someParameter': '<適当な値を渡す>'
};
/* ヘッダ設定 */
const requestHeaders = {
'Content-Type': 'application/json',
'Authorization': token
};
const requestOptions = {
'headers' : requestHeaders
};
/* APIを呼び出し */
const requestUrl = '<APIのエンドポイントのURL>';
try {
const response = await axios.post(requestUrl, JSON.stringify(requestPayload), requestOptions);
const content = response.data;
console.log(content.someParameter);
} catch(err) {
console.error(err);
}
} catch(err) {
console.error(err);
}
};
/* アクセストークン取得用 */
const getCognitoAccessToken = async (cognitoUrl, authFlow, clientId, username, password) => {
// リクエストボディ
const payload = {
AuthFlow: authFlow,
ClientId: clientId,
AuthParameters: {
USERNAME: username,
PASSWORD: password
}
};
// リクエストヘッダー
const headers = {
'Content-Type': 'application/x-amz-json-1.1',
'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth'
}
// リクエストオプション
const options = {
'headers' : headers
}
const response = await axios.post(cognitoUrl, JSON.stringify(payload), options);
const content = response.data;
return content.AuthenticationResult.IdToken;
}
Cognito接続用のユーザをあらかじめユーザプールに作成して、.envに設定しておきます。 Frontendの.envは次のように設定しています。
.env
REACT_APP_COGNITO_USER_POOL_CLIENT_ID=<CognitoユーザプールクライアントID>
REACT_APP_COGNITO_URL=https://cognito-idp.ap-northeast-1.amazonaws.com/
REACT_APP_COGNITO_USERNAME=<Cognito接続用ユーザのユーザ名>
REACT_APP_COGNITO_PASSWORD=<Cognito接続用ユーザのパスワード>
callAPI関数を呼び出せば、Backendで作成したAPI Gateway + Lambdaをコールして、レスポンスを取得できます。
留意事項
今回のサンプルでは、Cognito接続用のユーザパスワードをReact側に設定していますが、実際に運用に載せる場合はパスワードなどCredentialな情報は.envに記載しないでください。
具体的には、Proxy ServerをVercelなどで作って、FrontendでCredentialを持たないようにしてください。