こんにちは、広野です。
以前、以下の記事で 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 はまだまだ奥が深いです。引き続きいろいろな使い方を試していきたいと思います。
本記事が皆様のお役に立てれば幸いです。