こんにちは、広野です。
以前、以下の記事で Amazon Cognito のセルフサインアップ時にメールアドレスのドメイン名をチェックする方法を紹介しました。
実はセルフサインアップ時にと言いつつも、管理者が AWS マネジメントコンソールからユーザ登録するときもセルフサインアップと同様にチェックが走ってしまいます。ここでは、例外として管理者によるユーザ登録はドメイン名チェック無しにするように変更をかける、という続編記事を書きたいと思います。
本記事をお読みになる前に、前回記事をお読み頂くことをお勧めします。
やりたいこと
- ユーザのセルフサインアップ時のみ、メールアドレスのドメイン名をチェックするようにする。
- 管理者が AWS マネジメントコンソールからユーザ登録するときは、任意のドメイン名のメールアドレスを登録可能とする。
実現方法
- ドメイン名チェックを行う Lambda 関数に条件分岐を入れる。セルフサインアップをトリガーとしたユーザ登録の場合のみドメイン名チェックをする。
修正した AWS Lambda 関数
def lambda_handler(event, context):
try:
print(event) # Amazon Cognitoから渡されたユーザサインアップのリクエストデータ
triggersource = event['triggerSource'] # ユーザ登録のトリガーとなったソース情報を取得
email = event['request']['userAttributes']['email'] # リクエストデータからメールアドレスを抽出
print(email)
if triggersource == 'PreSignUp_SignUp': # トリガーソースがセルフサインアップだったら、ドメイン名チェックを実行
print('via self signup')
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) を返す
else: # トリガーソースがセルフサインアップ以外であれば、ドメイン名チェックを実行しない
print('via admin console')
return event
except Exception as e:
print(e)
Amazon Cognito からユーザ登録情報が AWS Lambda 関数に渡されるときに、triggerSource というキー名でトリガーがセルフサインアップなのか、マネジメントコンソールからの登録なのかを判別できます。
私が試した限り、以下の triggerSource がわかりました。
| ケース | triggerSource の値 | 備考 |
|---|---|---|
| セルフサインアップ | PreSignUp_SignUp | |
| MC からのユーザ登録 | PreSignUp_AdminCreateUser | Management Console から手動でユーザ登録したケース。 |
| API によるユーザ登録 | PreSignUp_AdminCreateUser | Lambda 関数から SDK でユーザ登録したケース。AWS CLI は未検証。 |
これで目的は果たせました。以下、AWS CloudFormation テンプレートは修正後のものを貼り付けておきます。
AWS CloudFormation テンプレート
AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates sample resources for checking domain name of user email addresses with Cognito and Lambda.
# ------------------------------------------------------------#
# 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) # Amazon Cognitoから渡されたユーザサインアップのリクエストデータ
triggersource = event['triggerSource'] # ユーザ登録のトリガーとなったソース情報を取得
email = event['request']['userAttributes']['email'] # リクエストデータからメールアドレスを抽出
print(email)
if triggersource == 'PreSignUp_SignUp': # トリガーソースがセルフサインアップだったら、ドメイン名チェックを実行
print('via self signup')
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) を返す
else: # トリガーソースがセルフサインアップ以外であれば、ドメイン名チェックを実行しない
print('via admin console')
return event
except Exception as e:
print(e)
DependsOn: LambdaTriggeredFromCognitoRole
おまけ
今回、ユーザ登録時のドメイン名チェック実行有無を条件分岐させましたが、以下のユーザ登録フローにあるメールアドレス存在確認も管理者によるユーザ登録のときに意図的にスキップすることができます。
メールアドレスを最初から「検証済み」にマークしておけば存在確認のステップは実行されません。
実運用では、要件によってこれらの手法を選別して頂けたらと思います。
まとめ
いかがでしたでしょうか?
Amazon Cognito はまだまだ奥が深いです。引き続きいろいろな使い方を試していきたいと思います。
本記事が皆様のお役に立てれば幸いです。



