はじめに
エブリーでソフトウェアエンジニアをしている本丸です。
最近、Amazon Cognitoのユーザープールから別のユーザープールにユーザーを移行する方法について調査する機会がありました。
Amazon Cognitoに関しては色々な箇所で使われていると思いますが、ユーザーの移行について触れる機会はそれほど多くないかと思いますので紹介しようかと思います。
Amazon Cognitoとは
Amazon Cognito(以降Cognitoと表記します)は、AWSが提供するウェブアプリとモバイルアプリ用のアイデンティティプラットフォームです。ユーザーの認証・承認を行うユーザープールとユーザーにAWSリソースへのアクセスを許可するアイデンティティプールを持っています。
DELISH KITCHENでは、ユーザーのメールアドレスの管理とメールアドレスを用いたサインインにCognitoのユーザープールを利用しています。
Lambdaトリガー
CognitoにはLambdaトリガーという機能があり、ユーザープールに対してサインインなどのイベントが発生した時に、それをトリガーとしてLambdaを呼び出すことができます。
公式ドキュメントからの引用ですが、Lambdaトリガーとして設定できるイベントには下記のようなものがあります。
トリガーの種類 | 説明 |
---|---|
認証前の Lambda トリガー | サインインリクエストを承認または拒否するカスタム検証 |
サインアップ前の Lambda トリガー | サインアップリクエストを承認または拒否するカスタム検証を実行する |
ユーザー移行の Lambda トリガー | 既存のユーザーディレクトリからユーザープールにユーザーを移行する |
カスタムメッセージの Lambda トリガー | メッセージの高度なカスタマイズとローカライズを実行する |
Cognitoのユーザープールへのインポート
ユーザープールへのインポート・移行方法は2つ用意されています。CSVを用いた方法とLambdaトリガーを用いた方法です。
CSVを用いたインポート
CSVを用いたインポートでは、指定されたフォーマットのCSVファイルを使用して一括でユーザーのインポートを行います。公式ドキュメントでは低労力で低コストなオプションとして紹介されていました。
こちらの方法では、セキュリティの観点からパスワードのインポートができないようになっています。そのため、移行の際にユーザー側でパスワードの変更が必要になります。
Lambdaトリガーを用いたインポート
Lambdaトリガーを利用したインポートでは、前述したLambdaトリガーを起点にユーザーの移行を行います(上述の表のユーザー移行の Lambda トリガー
が今回説明するトリガーです)。このトリガーは、ユーザーがサインインする時とパスワードのリセットを行うときに発火します。
こちらの方法では、パスワードも連携されるのですが認証フローにUSER_PASSWORD_AUTH
またはADMIN_USER_PASSWORD_AUTH
を指定し、ユーザー名とパスワードによる認証を行わなければならない点に注意です。
少しイメージしにくいかと思うので、次でもう少し詳細に説明します。
Lambdaトリガーを用いたユーザーの移行の実装
Lambdaトリガーを用いたユーザー移行の流れはおおよそ図のようになります。
ユーザーを移行したい先のアプリケーションでサインインもしくはパスワードリセットが呼び出されたのをトリガーにしてユーザー移行のLambda(図のuser migration lambda
)が呼び出されます。
ユーザー移行のLambdaの実装は次のようになります。
import { CognitoIdentityProviderClient, AdminInitiateAuthCommand, AdminGetUserCommand, CognitoIdentityProviderClientConfig, UserNotConfirmedException } from "@aws-sdk/client-cognito-identity-provider"; import { UserMigrationTriggerHandler } from "aws-lambda"; const userMigration: UserMigrationTriggerHandler = async (event) => { const config: CognitoIdentityProviderClientConfig = { region: 'ap-northeast-1', }; const client = new CognitoIdentityProviderClient(config); if(event.triggerSource == "UserMigration_Authentication") { const adminInitiateAuthCommand = new AdminInitiateAuthCommand({ UserPoolId: ${USER_POOL_ID}, ClientId: ${CLIENT_ID}, AuthFlow: "ADMIN_USER_PASSWORD_AUTH", AuthParameters: { "USERNAME": event.userName, "PASSWORD": event.request.password, }, }); // 認証できるかチェック try { await client.send(adminInitiateAuthCommand) } catch (e) { console.log(`user auth failed: ${e.message}`); throw e; } // cognitoに登録するユーザー情報構築 const adminGetUserCommand = new AdminGetUserCommand({ UserPoolId: process.env.DK_USER_POOL_ID, Username: event.userName, }); try { const response = await client.send(adminGetUserCommand); // 移行先のユーザーに持たせたい情報を詰め込む event.response.userAttributes = { "email": response.UserAttributes.find((attr) => attr.Name === "email")?.Value ?? "", "email_verified": response.UserAttributes.find((attr) => attr.Name === "email_verified")?.Value ?? "false", }; // 検証メールを送信しないため、下記を指定する event.response.messageAction = "SUPPRESS"; event.response.finalUserStatus = "CONFIRMED"; return event; } catch (e) { console.log(`get user failed: ${e.message}`); throw e; } } return event; };
このユーザー移行のLambdaの中で、移行元となるCognitoでユーザーの認証を行い、認証が成功した場合にユーザーの情報を取得します。その情報を移行先のCognitoに返すことでユーザーの移行を行います。
event.response
に渡すデータを変更することで移行先のユーザーに持たせたい情報を変更したり、ユーザーがそのメールアドレスの正当な所有者であるかを確認するメールを送信するかなどを操作することができます。
まとめ
Cognitoのユーザーの移行方法を調査して、ユーザーの移行を行うためにAWS公式で便利な機能が用意されていることを知ることができました。
2つの方法にそれぞれメリット・デメリットがあるかと思うので適切に使うようにしていきたいと思います。