Cognito ユーザープールのセルフサインアップ時の E メール検証フローがどの程度カスタマイズ出来るか確認してみた

2023.03.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

Cognito ユーザープールにはセルフサインアップの機能が備わっています。
基本的な流れとしては E メールなどのユーザー情報を入力したあとに、E メールで確認コードを受信し、それを使ってユーザーが確認済みとなり使用出来るようになるという流れです。

また、E メールはテンプレートを使ってカスタムすることも出来ますがどこまでカスタマイズが出来るのでしょうか。
コードを入力させるか、リンクをクリックさせるか、どの程度柔軟に設定が可能でしょうか。

本日は Cognito の API を使いながらいくつかの設定パターンを確認してみましたので紹介します。
結論だけ先にお伝えすると、このあたりはかなりカスタマイズ性は高いので Cognito を選択したことでアプリケーションの検証フロー要件を満たせなくなる可能性は低いのではないかなと思います。

デフォルトの動き

まず、セルフサインアップ機能を使う場合は Cognito ユーザープールの設定で自己登録を有効化する必要があります。

また、今回は検証メールを Cognito マネージドな機能で送信させたいので、「Cognito が検証と確認のためにメッセージを自動的に送信することを許可」を有効化し、セルフサインアップフローにて Cognito が自動で検証メールを送信出来るように設定します。
今回は検証対象は E メールアドレスを選択しました。

sign-up コマンドを使って、セルフサインアップを開始します。
実際には Web アプリケーションからセルフサインアップに必要な情報を入力し、この API が呼び出される想定でいます。画面で入力するのは E メールとパスワードの想定で、クライアント ID はフロントエンドの埋め込み、あるいは中継する BFF があればそちらから設定する想定です。
フロントエンドの作り込みや BFF との組み合わせが多少可能であればこのあたりは柔軟性ありそうです。要は SignUp API を呼び出せれば良いので。

% cat signup-param.json
{
    "ClientId": "7vursd1vcaaavq04oeqh689auv",
    "Username": "iwasa.takahito+user1@example.com",
    "Password": "hoge-password"
}
% aws cognito-idp sign-up --cli-input-json file://signup-param.json
{
    "UserConfirmed": false,
    "CodeDeliveryDetails": {
        "Destination": "i***@c***",
        "DeliveryMedium": "EMAIL",
        "AttributeName": "email"
    },
    "UserSub": "1d62d20d-a8aa-40f7-9ca6-72f778da11c6"
}

上記実行後、次のように E メール確認ステータスが未確認のユーザーが作成されています。

併せて入力した E メールアドレス宛に確認メールが送信されています。

E メールで送信された検証コードを使って confirm-sign-up で検証を行います。
ここも最終的には検証 API に検証コードが渡せれば良いのでフロントエンドの作り込みは必要ですが柔軟性は高そうです。

% cat confirmsignup-param.json
{
    "ClientId": "7vursd1vcaaavq04oeqh689auv",
    "Username": "iwasa.takahito+user1@example.com",
    "ConfirmationCode": "099489"
}
% aws cognito-idp confirm-sign-up --cli-input-json file://confirmsignup-param.json

検証後、次のようにユーザーのステータスが変更されました。

セルフサービスのサインアップが無効化されてる自己登録は出来ない

ユーザープール登録時に自己登録を有効化していましたが、これによって SignUp API がパブリックに呼び出せるようになっています。
自己登録を無効化するとセルフサインアップが出来なくなります。試してみましょう。

先程と同様にsign-upコマンドを実行してみましたが拒否されました。

% aws cognito-idp sign-up --cli-input-json file://signup-param.json

An error occurred (NotAuthorizedException) when calling the SignUp operation: SignUp is not permitted for this user pool

この場合は認可されていない状態でのユーザー作成は出来ないので、AdminCreateUser 権限を持つトークンを使って管理者によるユーザー作成(admin-create-user)を実行します。

% cat admincreateuser-param.json
{
    "UserPoolId": "ap-northeast-1_TcAYuRKNG",
    "Username": "iwasa.takahito+user4@example.com",
    "TemporaryPassword": "hoge-password",
    "ForceAliasCreation": true
}
% aws cognito-idp admin-create-user --cli-input-json file://admincreateuser-param.json
{
    "User": {
        "Username": "0e372c36-b9d6-4c3a-adea-ab53341fdc06",
        "Attributes": [
            {
                "Name": "sub",
                "Value": "0e372c36-b9d6-4c3a-adea-ab53341fdc06"
            },
            {
                "Name": "email",
                "Value": "iwasa.takahito+user4@example.com"
            }
        ],
        "UserCreateDate": "2023-03-21T09:24:55.756000+09:00",
        "UserLastModifiedDate": "2023-03-21T09:24:55.756000+09:00",
        "Enabled": true,
        "UserStatus": "FORCE_CHANGE_PASSWORD"
    }
}

この時、管理者は仮パスワードを入力しています。
ユーザーは管理者から一時パスワードを教えてもらって、その後パスワードを変更する必要がありますが、先程の自動通知設定が有効化されていると自動で招待メッセージが送信されます。

メッセージをカスタマイズする

ここまではユーザープールデフォルトのメッセージテンプレートを使っていました。
このメッセージテンプレートはユーザープールごとにカスタマイズ出来ます。

テンプレートを使ってカスタマイズ可能なものは次の 3 つです。

後述しますが、テンプレートで対応しきれないものは Lambda を使って更にカスタマイズすることが出来ます。
このテンプレートは埋め込み可能なプレースホルダなどもそう多くないのと制限事項もあるので、Lambda を使うケースは思ったよりもあると思います。

まずは、普通にメッセージテンプレートを使ってみましょうか。

メッセージテンプレート機能

メッセージテンプレートの仕様や利用可能なプレースホルダなどについては次の公式ドキュメントを確認しましょう。

サインアップ時の検証メッセージは、デフォルトでは次のように検証コードを送信するタイプになっています。

検証コード以外に検証用のリンクを送信することも出来ます。使ってみましょう。

変更後にセルフサインアップ操作を行ってみます。

% cat signup-param.json                                                           
{
    "ClientId": "7vursd1vcaaavq04oeqh689auv",
    "Username": "iwasa.takahito+user2@example.com",
    "Password": "hoge-password"
}
% aws cognito-idp sign-up --cli-input-json file://signup-param.json               

An error occurred (InvalidParameterException) when calling the SignUp operation: Cannot perform specific action because there does not exist a valid use pool domain associated with the user pool

エラーになりました。どうやら検証用のリンク先として、ユーザープールにドメインの関連付けが必要なようです。
ただし、ドメインが未関連付けのためにsign-upコマンドに失敗しましたが、ユーザーは作成されていました。検証コードの再送や管理者による検証が必要です。

ドメインを関連付けして再度サインアップしてみます。

% cat signup-param.json                                                           
{
    "ClientId": "7vursd1vcaaavq04oeqh689auv",
    "Username": "iwasa.takahito+user3@example.com",
    "Password": "hoge-password"
}
% aws cognito-idp sign-up --cli-input-json file://signup-param.json
{
    "UserConfirmed": false,
    "CodeDeliveryDetails": {
        "Destination": "i***@c***",
        "DeliveryMedium": "EMAIL",
        "AttributeName": "email"
    },
    "UserSub": "689a6abe-a462-4b0f-a2c5-6f495c44b511"
}

今度は成功しましたね。
次のようにメールが送信されていました。

一見妙な URL ですが、これは Cognito 自動送信メールにトラッキング用の URL が埋め込まれているためです。

リンクをクリックすると次のように Cognito マネージドな検証完了画面に遷移しました。
ここは Cognite Hosted UI のような感じですね。

ユーザー確認済みになりました。

Lambda トリガーでカスタマイズ

先程はテンプレートを使ってカスタマイズしました。
しかしいくつかプレースホルダーを試してみるとわかるのですが、検証メッセージではユーザー名などを埋め込むことが出来ません。

Verification code: 381583
User name: {username}

さらにカスタマイズを行いたい場合はカスタムメッセージ Lambda トリガー機能を使うことが出来ます。

いくつのトリガーが用意されており、Cognito ユーザープールの各種処理に割り込むことが出来ます。
ユーザープールのプロパティタブから Lambda トリガーを追加することが出来ます。

メッセージのカスタマイズを行う場合はトリガータイプに「メッセージング」を選択します。

Lambda トリガーに関数を関連付けすると、対象 Lambda 関数にリソースベースアクセスポリシーが自動で追加されるので、Cognito から Lambda への呼び出し権限周りは気にしなくても良いです。

Lambda 関数のイベントとしては次のような情報が渡されます。
リクエスト情報やコンテキストを活用し、responseを生成することでメッセージをカスタマイズ出来ます。

{
  version: '1',
  region: 'ap-northeast-1',
  userPoolId: 'ap-northeast-1_TcAYuRKNG',
  userName: 'fbfc4f55-6a61-4404-9dea-335005039330',
  callerContext: {
    awsSdkVersion: 'aws-sdk-unknown-unknown',
    clientId: '7vursd1vcaaavq04oeqh689auv'
  },
  triggerSource: 'CustomMessage_SignUp',
  request: {
    userAttributes: {
      sub: 'fbfc4f55-6a61-4404-9dea-335005039330',
      'cognito:email_alias': 'iwasa.takahito+user6@example.com',
      email_verified: 'false',
      'cognito:user_status': 'UNCONFIRMED',
      email: 'iwasa.takahito+user6@example.com'
    },
    codeParameter: '{####}',
    linkParameter: '{##Click Here##}',
    usernameParameter: null
  },
  response: { smsMessage: null, emailMessage: null, emailSubject: null }
}

テンプレートでリンクタイプを選択した時、自動で埋め込みリンクが作成されていましたが今回は検証用の URL を自前で組み立ててみましょう。
Cognito 組み込みの検証 URL の仕様は次のようになっています。

ここでは次のような関数を作成してみました。

index.mjs

export const handler = async (event) => {
  console.log(event);
  if (event.triggerSource === "CustomMessage_SignUp") {
    event.response.emailMessage = `https://hoge0321iwasa.auth.ap-northeast-1.amazoncognito.com/confirmUser/?client_id=${event.callerContext.clientId}&user_name=${event.request.userAttributes.sub}&confirmation_code=${event.request.codeParameter}`;
    event.response.emailSubject = "Hoge Subject.";
  }
  return event;
};

サインアップすると次のようなメールが送信されました。
うまくいってますね。

URL へアクセスすると先ほどと同様に Cognit Hosted UI の検証完了画面に遷移しました。

Hosted UI を使いたくないなーというケースは多いと思いますが、例えば API Gateway などで Cognito IDP の confirm-sign-up を統合した API を用意してやってカスタムエンドポイントで検証させることも出来そうです。

いずれにせよ Lambda トリガーを使えば、検証コードを任意のフォーマットで受け渡すことが出来るのでかなり柔軟に対応出来そうです。

さいごに

本日は Cognito ユーザープールのセルフサインアップ時の E メール検証フローがどの程度カスタマイズ出来るか確認してみました。

Cognito ユーザープールの検証フローではかなり柔軟性のあるカスタマイズオプションが提供されています。
テンプレート機能は制限が多めなので、最終的には Lambda トリガーを使うことが多そうだなという印象ではありますが、割り込める箇所が多いのでセルフサインアップ時の検証周りも、独自で行わずにまずは Cognito ユーザープールにお任せしても良さそうに思えますね。