AWS Amplify Gen 2 でユーザのサインアップ時にメール通知する
こんにちは、なおにしです。
AWS Amplify Gen 2 のAmplify Authを使用してユーザがサインアップする際、サインアップしたことをメール通知してみる機会がありましたのでご紹介します。
はじめに
AWS Amplify Gen 2 のAuth機能では、トリガー関数を定義することで認証フロー中の動作をカスタマイズすることが可能です。
ドキュメントに記載のとおり、このトリガーはAmazon Cognito のユーザープールにおけるLambdaトリガーに変換されます。Cognitoのコンソール画面でいうと以下が該当箇所です。
実際にLambdaトリガーを追加する画面に遷移すると、認証フローにおける以下のようなタイミングでLambdaによる処理を追加できることが分かります。
Amplify Gen 2 のドキュメントではサインアップ前のトリガーとして、メールアドレスに基づいてユーザのサインアップを許可または拒否する例が紹介されています。弊社記事にも実際にやってみた内容がありますのでご参照ください。
今回やりたいこととしては、新規ユーザがサインアップしたことをメール通知するので、「確認後 トリガー」を使用すれば良さそうです。Cognito の該当ドキュメントは以下になります。
一方、Amplify Gen 2 で定義されているトリガーとしては以下のとおりです。
- createAuthChallenge
- customMessage
- defineAuthChallenge
- postAuthentication
- postConfirmation
- preAuthentication
- preSignUp
- preTokenGeneration
- userMigration
- verifyAuthChallengeResponse
上記のうち、「postConfirmation」トリガーを使用することで実装できそうです。目的は異なりますが、「postConfirmation」を使用した例も紹介されています。
ここまでであれば前述の「サインアップ前 トリガー」の実装例を「確認後 トリガー」に変更するだけなのですが、今回はメール通知に Amazon SES を使用します。この場合、LambdaからSESを利用するので以下で紹介されているとおりIAMポリシーを対象のトリガー関数に適用する必要があります。
Amplify Gen 2によって作成されたLambda関数にアタッチされているIAMロールに手動でインラインポリシーを追加しても良いのですが、せっかくならAmplify Gen 2 のコード内でアクセス許可を追加したいところです。
というわけで、以下ドキュメントに記載の内容を参考に、CDKを利用することで対象のLambda関数のIAMロールにSESを利用するためのインラインポリシーを追加してみます。
やってみた
事前準備
クイックスタートが完了した状態から始めます。私の環境では前回の記事の内容が反映された状態になっていますが、本記事の内容はクイックスタートが完了した状態から始めても問題ありません。
また、SESについてはサンドボックス環境でも問題ありませんが、メール送信ができる状態になっている想定です。
パッケージのアップデート
本記事の主旨とは異なるのでスキップしても問題ないのですが、備忘の目的で記載します。ドキュメントのクイックスタートにあるテンプレートからプロジェクトを開始した場合、含まれているパッケージが執筆時点でもかなり古くなってきています。
以下、執筆時点でのパッケージ情報を以下コマンドで出力した結果です。私の環境で諸々検証をした後の状態なので、テンプレートからコピーした直後の内容とは異なるかと思いますのであくまで参考です。
npm outdated | awk '{print $1, $2, $3, $4}' | awk 'NR==1 {print "| " $1 " | " $2 " | " $3 " | " $4 " |"; print "| --- | --- | --- | --- |"} NR>1 {print "| " $1 " | " $2 " | " $3 " | " $4 " |"}'
Package | Current | Wanted | Latest |
---|---|---|---|
@aws-amplify/backend | 1.5.0 | 1.16.1 | 1.16.1 |
@aws-amplify/backend-cli | 1.2.9 | 1.7.2 | 1.7.2 |
@aws-amplify/ui-react | 6.5.5 | 6.11.2 | 6.11.2 |
@types/react | 18.3.12 | 18.3.21 | 19.1.4 |
@types/react-dom | 18.3.1 | 18.3.7 | 19.1.5 |
@typescript-eslint/eslint-plugin | 7.18.0 | 7.18.0 | 8.32.1 |
@typescript-eslint/parser | 7.18.0 | 7.18.0 | 8.32.1 |
@vitejs/plugin-react | 4.3.3 | 4.4.1 | 4.4.1 |
aws-amplify | 6.6.6 | 6.14.4 | 6.14.4 |
aws-cdk | 2.163.1 | 2.1016.0 | 2.1016.0 |
aws-cdk-lib | 2.163.1 | 2.196.0 | 2.196.0 |
esbuild | 0.20.2 | 0.20.2 | 0.25.4 |
eslint | 8.57.1 | 8.57.1 | 9.27.0 |
eslint-plugin-react-hooks | 4.6.2 | 4.6.2 | 5.2.0 |
eslint-plugin-react-refresh | 0.4.13 | 0.4.20 | 0.4.20 |
react | 18.3.1 | 18.3.1 | 19.1.0 |
react-dom | 18.3.1 | 18.3.1 | 19.1.0 |
tsx | 4.19.1 | 4.19.4 | 4.19.4 |
typescript | 5.6.3 | 5.8.3 | 5.8.3 |
vite | 5.4.10 | 5.4.19 | 6.3.5 |
上記のとおり全体的に古くなってきていることもあり、アップデートにはnpm-check-updatesを使います。
ncu
コマンドの結果は以下のとおりです。Amplifyのバージョンが上がるだけではなくReactのバージョンが18→19になっていたりと、本番環境であればいきなり実行はできない内容ですが、今回は対象が検証用のテンプレートなので深く考えずに実行してみます。
@aws-amplify/backend ^1.5.0 → ^1.16.1
@aws-amplify/backend-cli ^1.2.9 → ^1.7.2
@aws-amplify/ui-react ^6.5.5 → ^6.11.2
@types/react ^18.2.66 → ^19.1.4
@types/react-dom ^18.2.22 → ^19.1.5
@typescript-eslint/eslint-plugin ^7.2.0 → ^8.32.1
@typescript-eslint/parser ^7.2.0 → ^8.32.1
@vitejs/plugin-react ^4.2.1 → ^4.4.1
aws-amplify ^6.6.6 → ^6.14.4
aws-cdk ^2.138.0 → ^2.1016.0
aws-cdk-lib ^2.138.0 → ^2.196.0
constructs ^10.3.0 → ^10.4.2
esbuild ^0.20.2 → ^0.25.4
eslint ^8.57.0 → ^9.27.0
eslint-plugin-react-hooks ^4.6.0 → ^5.2.0
eslint-plugin-react-refresh ^0.4.6 → ^0.4.20
react ^18.2.0 → ^19.1.0
react-dom ^18.2.0 → ^19.1.0
tsx ^4.7.2 → ^4.19.4
typescript ^5.4.5 → ^5.8.3
vite ^5.4.10 → ^6.3.5
ncu -u
コマンドでpackage.jsonを更新し、npm install
します。出力結果は割愛しますが、特に問題なくアップデートされ、Todoアプリも動いてはいますので今回はこのままアップデートを適用した環境で継続します。
なお、アップデートが完了するとnpm outdated
コマンドは実行しても何も出力されなくなり、ncu
コマンドについては以下のとおり出力されるようになりました。
All dependencies match the latest package versions :)
Lambdaトリガーの作成
今回はAmplify AuthとしてCognitoで利用するLambdaトリガーのためのLambda関数ですが、設定方法はAmplify Functionsとして設定するLambda関数と同じ流れです。
このため、ドキュメントにあるとおり新しいディレクトリとリソースファイル、および対応するハンドラーファイルを作成していきます。
Amplify Authで利用するため、ディレクトリについてはamplify/auth配下に「post-confirmation」を作成します。
リソースファイルではdefineFunction
を定義します。また、Lambdaの環境変数についてもこちらで定義できるので、メール通知先アドレスをメール送信元アドレスを設定します。
import { defineFunction } from '@aws-amplify/backend';
export const postConfirmation = defineFunction({
name: 'post-confirmation',
environment: {
TARGET_ADDRESS: 'nishimura.naoki@example.com',
SOURCE_ADDRESS: 'Amplifyテスト <naonishi@mail.example.com>'
}
});
続いて、対応するハンドラーファイルを作成します。SESを利用するので、パッケージを未インストールの場合は追加しておきます。
$ npm install @aws-sdk/client-ses
せっかくなのでユーザ属性の「name」も取得してメール通知するように実装してみます。
import type { PostConfirmationTriggerHandler } from 'aws-lambda';
import { env } from '$amplify/env/post-confirmation';
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
// SESクライアントの初期化
const sesClient = new SESClient({ region: process.env.REGION || 'ap-northeast-1' });
export const handler: PostConfirmationTriggerHandler = async (event) => {
try {
const userAttributes = event.request.userAttributes;
const email = userAttributes['email'];
const name = userAttributes['name'] || '(name取得失敗)';
const targetEmails: string[] = env.TARGET_ADDRESS.split(',').map((email: string) => email.trim());
const sourceEmail = env.SOURCE_ADDRESS;
const params = {
Destination: {
ToAddresses: targetEmails
},
Message: {
Body: {
Text: {
Data: `新規ユーザがサインアップしました。
【ユーザ情報】
Eメールアドレス: ${email}
名前: ${name}
ユーザーID: ${event.userName}
`,
},
},
Subject: {
Data: '新規ユーザサインアップ通知',
},
},
Source: sourceEmail,
};
await sesClient.send(new SendEmailCommand(params));
} catch (error) {
console.error('エラーが発生しました:', error);
}
return event;
};
Sandbox環境を起動せずにVSCodeで編集している場合は以下のエラーが表示されるかと思いますが、設定完了後にSandboxを起動すれば解消されるのでそのままで問題ありません。
モジュール '$amplify/env/post-confirmation' またはそれに対応する型宣言が見つかりません。ts(2307)
続いて、作成したdefineFunction
のリソースをAuthのリソースに設定します。この際、上記コードのとおり「name」属性もメール通知するので、対応するfullname
属性を必須パラメータとして定義します。
import { defineAuth } from '@aws-amplify/backend';
+ import { postConfirmation } from './post-confirmation/resource';
/**
* Define and configure your auth resource
* @see https://docs.amplify.aws/gen2/build-a-backend/auth
*/
export const auth = defineAuth({
loginWith: {
email: true,
},
+ triggers: {
+ postConfirmation
+ },
+ // https://docs.amplify.aws/react/build-a-backend/auth/concepts/user-attributes/
+ userAttributes: {
+ // Maps to Cognito standard attribute 'name'
+ fullname: {
+ mutable: true,
+ required: true,
+ },
+ }
});
ここまで設定できた段階でSandboxを起動してみます。前述のエラーが解消され、デプロイが完了したらローカル環境を立ち上げます。
$ npm run dev
ローカルホストにアクセスし、表示された認証画面で「Create Account」を選択すると、以下のように「Name」欄が追加されていることが分かります。実際にユーザを追加してみます。
入力したメールアドレスに確認コードが送信され、コードを入力すると問題なくサインアップできました。一方で、IAMロールの設定はまだ適用していないため、想定どおりメール通知は届きませんでした。
作成されたLambda関数のCloudWatch Logsを確認すると、以下のようにエラーが出力されていました。
ERROR エラーが発生しました: AccessDenied: User
arn:aws:sts::012345678912:assumed-role/amplify-amplifyvitereactt-postconfirmationlambdaSer-p3VSQEHxztXC/amplify-amplifyvitereactt-postconfirmationlambda19-iARufEs8MPKo' is not authorized to perform
ses:SendEmail' on resource `arn:aws:ses:ap-northeast-1:012345678912:identity/nishimura.naoki@example.com'
Lambda関数のIAMロールを設定
それでは改めてLambda関数に適用されているIAMロールにSES実行を許可するインラインポリシーを追加していきます。
他のリソースへのアクセスを許可する方法として、ドキュメントでは以下2つの方法が記載されています。
access
プロパティの使用- CDKの使用
今回はLambdaトリガーを利用するためにdefineFunction
を定義していますが、defineFunction
の型定義を参照してもaccess
プロパティは存在しないようなので、CDKを使用したアクセス許可の設定となります。
ところが、今回作成したLambda関数はCognito から呼び出されるLambdaトリガーであるため、ドキュメントにあるように定義したLambda関数自体をバックエンドに追加していない状態です。
このような場合、CDKを使用してIAMロールにポリシーを追加するにはどうすれば良いのかと考えたのですが、結果的には以下のように改めてAmplify FunctionsのLambda関数としてバックエンドに追加し、ドキュメントに記載のとおりポリシーを追加することが可能でした。
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
+ import { postConfirmation } from "./auth/post-confirmation/resource"
+ import * as iam from "aws-cdk-lib/aws-iam"
- defineBackend({
+ const backend = defineBackend({
auth,
data,
+ postConfirmation
});
+ const statement = new iam.PolicyStatement({
+ actions: ["ses:SendEmail", "ses:SendRawEmail"],
+ effect: iam.Effect.ALLOW,
+ resources: ["*"],
+ })
+ const postConfirmationLambda = backend.postConfirmation.resources.lambda
+ postConfirmationLambda.addToRolePolicy(statement);
改めてデプロイし、サインアップを試したところ、以下のようにメール通知を受信することができました。
Lambda関数に設定された実行ロールは以下のように作成されていました。
まとめ
Amplify Auth で使用しているLambda関数のため、IAMロールを編集するための何らかの設定項目がAuth側にあるのでは?と考えてしまい、諸々調査しても方法を見つけることができず、結局はAmplify Functions としてバックエンドに追加した上でIAMロールを設定すれば良いということに辿り着くまでに少し遠回りをしてしまったためまとめてみました。
本記事がどなたかのお役に立てれば幸いです。