こんにちは、広野です。
先日、AWS Amplify + Amazon Cognito + React アプリのサインイン機能に あらかじめ登録したドメイン名のメールアドレスを持つユーザのみ、セルフサインアップを受け付ける 機能を追加したので、具体的な方法を紹介します。
やりたいこと

前提として、アプリに Amplify UI v2 の出来合いのサインイン UI が組込済みで、Amazon Cognito と連携済みです。
メールアドレスをユーザとして使用する設定にしてあります。
実現方法
Amazon Cognito の「サインアップ前 Lambdaトリガー」を使用します。
Amazon Cognito のサインアップフローの中で、サインアップ前に任意の Lambda 関数を挟み込みます。その Lambda 関数の中でメールアドレスのドメイン名をチェックし、結果を Amazon Cognito に返します。それ以外は Amazon Cognito の標準フローのままです。
設定・解説
Lambda関数
AWS公式ドキュメントにはそのまま使えるサンプルはなかったので、こちらのコードが参考になりましたら幸いです。
コード (Python)
Lambda関数のコードサンプルです。
def lambda_handler(event, context):
try:
print(event) # Amazon Cognitoから渡されたユーザサインアップのリクエストデータ
email = event['request']['userAttributes']['email'] # リクエストデータからメールアドレスを抽出
print(email)
domain = email.split('@')[1] # メールアドレスからドメイン名だけを抽出
allowedDomains = [ "example1.com", "example2.com", "example3.com" ] # サインアップを許可するドメイン名群
if domain in allowedDomains: # ユーザ入力のドメイン名が許可するドメイン名群に含まれているかチェック
print('domain matched')
return event # 含まれていれば、Amazon Cognito に event をそのまま返す
else:
print('domain unmatched')
return None # 含まれていなければ、Amazon Cognito に null (PythonではNone) を返す
except Exception as e:
print(e)
Amazon Cognito から渡されるデータは、上記 Lambda 関数では event に格納され、以下の JSON フォーマットになっています。このデータにある限りの項目であれば Lambda 関数の中で加工・変更し Amazon Cognito に返して処理を継続させることができます。
{
"version": "1",
"region": "ap-northeast-1",
"userPoolId": "ap-northeast-1_XXXXXXXXX",
"userName": "adea79d7-8f6f-4ff3-9552-caf0ff59239d",
"callerContext": {
"awsSdkVersion": "aws-sdk-unknown-unknown",
"clientId": "xxxxxxxxxxxxxxxxxxxxxx"
},
"triggerSource": "PreSignUp_SignUp",
"request": {
"userAttributes": {
"email": "test@example.com"
},
"validationData": null
},
"response": {
"autoConfirmUser": false,
"autoVerifyEmail": false,
"autoVerifyPhone": false
}
}
サインアップリクエストをそのまま進めたければ、Amazon Cognito から渡された event を加工せず return で返します。
サインアップリクエストを却下したいときには、event の代わりに null を返します。Pythonでは None と記述することに注意しましょう。
実は却下時の return での返し方がAWS公式ドキュメントに見当たりませんでした。ですが、実際に Amazon Cognito に null を返すと、以下のようにLambda関数やJSON のエラーとして画面表示され、処理が終了します。いろいろと調べて試しましたが、Amplify UI の出来合いの UI を使用する限りはエラーメッセージのカスタマイズをすることは出来なさそうです。

IAM ロール
この Lambda 関数は他のサービスを呼び出すことはないので、Lambda 関数の実行ログを Amazon CloudWatch Logs に残すための権限と、必要に応じてですが AWS X-Ray にログを残せる権限を付けておくとよいでしょう。
マネージドポリシーARN
- aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- aws:iam::aws:policy/AWSXRayDaemonWriteAccess
リソースベースポリシー
忘れてはいけないのは、この Lambda 関数が Amazon Cognito から呼び出されることを許可する権限です。
Lambda 側で以下のようなリソースベースポリシーを追加します。
詳細なパラメータはブログ最下部に CloudFormation テンプレートを紹介していますので、そちらをご覧ください。
Amazon Cognito 側 Lambda トリガー設定
マネジメントコンソールでは、以下のように「サインアップ前 Lambda トリガー」を追加します。
この連携設定をすることで、ユーザのオンラインサインアップ時、サインアップの確定前に必ず指定したLambda関数が呼び出されることになります。
CloudFormation テンプレート
以下、詳細なパラメータを含む CloudFormation テンプレートのサンプルです。
- Amazon Cognito
- ユーザプール
- アップクライアント
- メールアドレスのみをユーザ名とする
- TOTP MFA 必須
- AWS Lambda
- サインアップを許可するドメイン名は CloudFormation でパラメータ化
- リソースベースポリシー
- IAM ロール
- Lambda 関数用
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation template example
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
DomainName:
Type: String
Description: Domain name for URL. xxxxx.xxx (e.g. example.com)
Default: example.com
MaxLength: 40
MinLength: 5
SubDomainName:
Type: String
Description: Sub domain name for URL. xxxx.example.com
Default: subdomain
MaxLength: 20
MinLength: 1
CognitoAdminEmail:
Type: String
Description: Cognito Admin e-mail address. (e.g. xxx@xxx.xx)
Default: xxxxx@example.com
MaxLength: 100
MinLength: 5
AllowedUserEmailDomains:
Description: Domain list to allow user sign up. Each domains must be comma delimited and double quoted.
Type: String
Default: '"example1.com","example2.com","example3.com"'
Resources:
# ------------------------------------------------------------#
# Lambda (triggered from Cognito) Role (IAM)
# ------------------------------------------------------------#
LambdaTriggeredFromCognitoRole:
Type: AWS::IAM::Role
Properties:
RoleName: LambdaTriggeredFromCognitoRole
Description: This role grants Lambda functions triggered from Cognito basic priviledges.
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
# ------------------------------------------------------------#
# Cognito Lambda Invocation Permission
# ------------------------------------------------------------#
CognitoLambdaInvocationPermissionPresignup:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt LambdaCognitoPresignup.Arn
Action: lambda:InvokeFunction
Principal: cognito-idp.amazonaws.com
SourceAccount: !Sub ${AWS::AccountId}
SourceArn: !GetAtt UserPool.Arn
DependsOn:
- LambdaCognitoPresignup
- UserPool
# ------------------------------------------------------------#
# Cognito
# ------------------------------------------------------------#
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: ExampleUserPool
MfaConfiguration: "ON"
EnabledMfas:
- SOFTWARE_TOKEN_MFA
Policies:
PasswordPolicy:
MinimumLength: 8
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
TemporaryPasswordValidityDays: 180
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
AdminCreateUserConfig:
AllowAdminCreateUserOnly: false
AutoVerifiedAttributes:
- email
DeviceConfiguration:
ChallengeRequiredOnNewDevice: false
DeviceOnlyRememberedOnUserPrompt: false
EmailConfiguration:
EmailSendingAccount: DEVELOPER
From: !Sub ${CognitoAdminEmail}
ReplyToEmailAddress: !Sub ${CognitoAdminEmail}
SourceArn: !Sub arn:aws:ses:ap-northeast-1:${AWS::AccountId}:identity/${CognitoAdminEmail}
EmailVerificationMessage: Verification code: {####}"
EmailVerificationSubject: Verification code"
LambdaConfig:
PreSignUp: !GetAtt LambdaCognitoPresignup.Arn
UsernameAttributes:
- email
UsernameConfiguration:
CaseSensitive: false
UserPoolAddOns:
AdvancedSecurityMode: "OFF"
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
ClientName: example-appclient
GenerateSecret: false
RefreshTokenValidity: 3
AccessTokenValidity: 6
IdTokenValidity: 6
ExplicitAuthFlows:
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
PreventUserExistenceErrors: ENABLED
SupportedIdentityProviders:
- COGNITO
CallbackURLs:
- !Sub https://${SubDomainName}.${DomainName}/index.html
LogoutURLs:
- !Sub https://${SubDomainName}.${DomainName}/index.html
DefaultRedirectURI: !Sub https://${SubDomainName}.${DomainName}/index.html
AllowedOAuthFlows:
- implicit
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthScopes:
- email
- openid
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
LambdaCognitoPresignup:
Type: AWS::Lambda::Function
Properties:
FunctionName: CognitoPresignup
Description: Lambda Function triggered from Cognito before user self signup to check the user's email domain
Runtime: python3.9
Timeout: 3
MemorySize: 128
Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn
Handler: index.lambda_handler
Code:
ZipFile: !Sub |
def lambda_handler(event, context):
try:
print(event)
email = event['request']['userAttributes']['email']
print(email)
domain = email.split('@')[1]
allowedDomains = [ ${AllowedUserEmailDomains} ]
if domain in allowedDomains:
print('domain matched')
return event
else:
print('domain unmatched')
return None
except Exception as e:
print(e)
DependsOn: LambdaTriggeredFromCognitoRole
まとめ
細かいことを書き出すと長くなってしまうので、Amazon Cognito と AWS Lambda を中心にコード例や設定例を記載いたしました。アプリ側のことは一切記載していませんので、ある程度そこはわかっている方前提の記事となっております。ご了承ください。
この記事がみなさまのお役に立てると幸いです。



