こんにちは、広野です。
2025 年 6 月に Amazon Cognito マネージドログインに AWS WAF の Web ACL をアタッチすることができるようになりました。
何ら真新しいことではないのですが、気を付けた方がよい点があるので書いておきます。
公式ドキュメント
以下に公式ドキュメントがあります。
アタッチした AWS WAF Web ACL によるチェック対象は以下になります。
- Managed login and the classic hosted UI
- Public API operations
つまり、UI と API なんですが。さらっと書かれすぎているので、以降、別ブログ記事の図を引用して説明します。
どこがチェック対象なのか
図に示すと、以下の赤丸の 2 箇所がチェック対象になります。
もう少し概念レベルで Amazon Cognito ユーザープールを機能分割すると、以下のように表現できます。
Amazon Cognito のマネージドログイン UI と、API がチェック対象になるのですが、それぞれのソースが異なります。
ソース IP アドレス制限をかけるときの注意
このため、Amazon Cognito マネージドログインを使用する環境でソース IP アドレス制限をかけるときには注意事項があります。
ソースは複数箇所あると考えた方がよいです。上記のケースでは、ユーザーデバイスと Amazon EC2 インスタンスの IP アドレスを両方許可するようにしてあげないと、認証フローが通りません。
おまけ AWS CloudFormation テンプレート
AWS Cognito に AWS WAF をアタッチする AWS CloudFormation テンプレートを参考までに貼り付けます。
今回検証したのは特定のソース IP アドレス (サブネット) を許可する設定です。
アプリケーションクライアントの設定は省略しています。Amazon Cognito ユーザープールと、AWS WAF およびそのログ保存に絞っています。登場する IP アドレスは架空のものです。
AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a Cognito user pool with Managed Login.
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
SystemName:
Type: String
Description: System name. use lower case only. (e.g. example)
Default: example
MaxLength: 10
MinLength: 1
SubName:
Type: String
Description: System sub name. use lower case only. (e.g. prod or dev)
Default: dev
MaxLength: 10
MinLength: 1
SesId:
Type: String
Description: Amazon SES ID for sending emails. (email addreess or domain)
Default: xxxx.xxx
MaxLength: 100
MinLength: 5
SesConfigurationSet:
Type: String
Description: Amazon SES configuration set for sending emails.
Default: xxxxxxxxxxxxxxxx
MaxLength: 100
MinLength: 5
CognitoReplyTo:
Type: String
Description: Cognito Reply-to email address. (e.g. xxx@xxx.xxx)
Default: xxxxx@xxx.xxx
MaxLength: 100
MinLength: 5
CognitoEmailFrom:
Type: String
Description: Cognito e-mail from address. (e.g. xxx@xxx.xxx)
Default: xxxxx@xxx.xxx
MaxLength: 100
MinLength: 5
LogRetentionDays:
Type: Number
Description: The retention period (days) for AWS WAF logs. Enter an integer between 35 to 540.
Default: 365
MaxValue: 540
MinValue: 35
SourceIpv4RangeList:
Type: CommaDelimitedList
Description: The comma delimited list of allowed source IPv4 address range (CIDR) except /0 to access this web site. (e.g. xxx.xxx.xxx.xxx/xx,yyy.yyy.yyy.yyy/yy)
Default: "8.8.8.8/32,8.8.4.4/32"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "General Configuration"
Parameters:
- SystemName
- SubName
- Label:
default: "Email Configuration"
Parameters:
- SesId
- SesConfigurationSet
- CognitoReplyTo
- CognitoEmailFrom
- Label:
default: "Security Configuration"
Parameters:
- LogRetentionDays
- SourceIpv4RangeList
Resources:
# ------------------------------------------------------------#
# Cognito
# ------------------------------------------------------------#
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${SystemName}-${SubName}
MfaConfiguration: "OFF"
Policies:
PasswordPolicy:
MinimumLength: 8
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
TemporaryPasswordValidityDays: 180
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
AdminCreateUserConfig:
AllowAdminCreateUserOnly: true
AutoVerifiedAttributes:
- email
DeviceConfiguration:
ChallengeRequiredOnNewDevice: false
DeviceOnlyRememberedOnUserPrompt: false
EmailConfiguration:
ConfigurationSet: !Ref SesConfigurationSet
EmailSendingAccount: DEVELOPER
From: !Sub "${SystemName}-${SubName} admin <${CognitoEmailFrom}>"
ReplyToEmailAddress: !Ref CognitoReplyTo
SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/${SesId}
EmailVerificationMessage: !Sub "${SystemName}-${SubName} Verification code: {####}"
EmailVerificationSubject: !Sub "${SystemName}-${SubName} Verification code"
UsernameConfiguration:
CaseSensitive: false
UserPoolAddOns:
AdvancedSecurityMode: "OFF"
UserPoolTags:
Cost: !Sub ${SystemName}-${SubName}
UserPoolTier: ESSENTIALS
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
Domain: !Sub ${SystemName}-${SubName}
ManagedLoginVersion: 2
UserPoolId: !Ref UserPool
# ------------------------------------------------------------#
# WAF Web Acl for Cognito
# ------------------------------------------------------------#
WebAclCognito:
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub ${SystemName}-${SubName}-cognito
Description: WAFv2 WebACL for Cognito
Scope: REGIONAL
DefaultAction:
Block: {}
Rules:
- Name: !Sub ${SystemName}-${SubName}-cognito-IpWhiteList
Action:
Allow: {}
Priority: 0
Statement:
IPSetReferenceStatement:
Arn: !GetAtt WAFv2Ipv4WhiteList.Arn
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub ${SystemName}-${SubName}-cognito-IpWhiteList
SampledRequestsEnabled: true
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub ${SystemName}-${SubName}-cognito
SampledRequestsEnabled: true
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
DependsOn:
- WAFv2Ipv4WhiteList
WAFv2Ipv4WhiteList:
Type: AWS::WAFv2::IPSet
Properties:
Name: !Sub ${SystemName}-${SubName}-Ipv4WhiteList
Description: WAF v2 IPv4 white list for the IP-based restricted access
IPAddressVersion: IPV4
Addresses: !Ref SourceIpv4RangeList
Scope: REGIONAL
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
WebACLAssociationCognito:
Type: AWS::WAFv2::WebACLAssociation
Properties:
ResourceArn: !GetAtt UserPool.Arn
WebACLArn: !GetAtt WebAclCognito.Arn
DependsOn:
- UserPool
- WebAclCognito
WebAclLoggingConfigurationCognito:
Type: AWS::WAFv2::LoggingConfiguration
Properties:
ResourceArn: !GetAtt WebAclCognito.Arn
LogDestinationConfigs:
- !GetAtt S3BucketWafLogs.Arn
LoggingFilter:
DefaultBehavior: KEEP
Filters:
- Behavior: KEEP
Conditions:
- ActionCondition:
Action: BLOCK
Requirement: MEETS_ANY
DependsOn:
- S3BucketWafLogs
- WebAclCognito
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3BucketWafLogs:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub aws-waf-logs-${SystemName}-${SubName}-cognito
LifecycleConfiguration:
Rules:
- Id: AutoDelete
Status: Enabled
ExpirationInDays: !Ref LogRetentionDays
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# Cognito
CognitoUserPoolId:
Value: !Ref UserPool
Export:
Name: !Sub CognitoUserPoolId-${SystemName}-${SubName}
CognitoUserPoolArn:
Value: !GetAtt UserPool.Arn
Export:
Name: !Sub CognitoUserPoolArn-${SystemName}-${SubName}
CognitoUserPoolProviderName:
Value: !GetAtt UserPool.ProviderName
Export:
Name: !Sub CognitoUserPoolProviderName-${SystemName}-${SubName}
CognitoUserPoolProviderUrl:
Value: !GetAtt UserPool.ProviderURL
Export:
Name: !Sub CognitoUserPoolProviderUrl-${SystemName}-${SubName}
まとめ
いかがでしたでしょうか。
今回は小ネタでした。Amazon Cognito 周りの通信パスをご存知の方にとっては当たり前と思いましたが、意外と知られていないかもな?と思ったので書きました。
本記事が皆様のお役に立てれば幸いです。



