Tarek Cheikh
Founder & AWS Security Expert
Amazon Cognito is the front door to your application. It handles user sign-up, sign-in, multi-factor authentication, and token issuance for millions of applications worldwide. Every authentication decision -- who can sign up, how passwords are validated, whether MFA is required, how tokens are issued and verified -- flows through Cognito. A misconfigured user pool or identity pool can expose your entire application to credential stuffing, account takeover, privilege escalation, and data exfiltration.
Authentication is the most attacked surface in any application. Credential stuffing attacks increased 65% between 2024 and 2025, and compromised credentials remain the leading cause of cloud breaches. Amazon Cognito provides powerful security features -- advanced threat protection, adaptive authentication, WAF integration, and compromised credential detection -- but many of these capabilities must be explicitly enabled and configured. Out-of-the-box defaults leave significant gaps.
This guide covers 12 battle-tested Cognito security best practices, each with real AWS CLI commands, configuration examples, and audit procedures. Whether you are securing a new user pool or hardening an existing deployment, these practices will significantly reduce your authentication attack surface.
MFA is the single most effective control against account takeover. Without MFA, a stolen password is all an attacker needs. Cognito supports TOTP (authenticator apps), SMS OTPs, and email OTPs as second factors. TOTP should be your default recommendation -- it is free, works offline, and is not vulnerable to SIM-swapping attacks that plague SMS-based MFA.
Set MFA to REQUIRED for all user pools that handle sensitive data. If you must start with optional MFA, create a migration plan to move to required MFA within a defined timeframe.
# Enable required MFA with TOTP and SMS as allowed factors
aws cognito-idp set-user-pool-mfa-config \
--user-pool-id us-east-1_EXAMPLE \
--mfa-configuration ON \
--software-token-mfa-configuration Enabled=true \
--sms-mfa-configuration "SmsAuthenticationMessage='Your code is {####}',SmsConfiguration={SnsCallerArn=arn:aws:iam::123456789012:role/CognitoSNSRole,ExternalId=cognito-external-id}"
# Verify MFA configuration
aws cognito-idp describe-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--query "UserPool.MfaConfiguration"
Important: Users cannot receive MFA and password-reset codes at the same email address or phone number. If they use email OTPs for MFA, they must use SMS for account recovery, and vice versa. Plan your recovery flow before enabling MFA.
Weak passwords are the easiest attack vector. Cognito allows you to enforce minimum length, character complexity, and -- with the Essentials or Plus feature tiers -- password reuse prevention. Modern guidance from NIST SP 800-63B favors longer passwords over complex ones. A 14-character minimum with mixed case is more secure and user-friendly than an 8-character requirement with mandatory symbols.
# Update password policy with strong requirements
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--policies '{
"PasswordPolicy": {
"MinimumLength": 14,
"RequireUppercase": true,
"RequireLowercase": true,
"RequireNumbers": true,
"RequireSymbols": false,
"TemporaryPasswordValidityDays": 1,
"PasswordHistorySize": 12
}
}'
# Verify password policy
aws cognito-idp describe-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--query "UserPool.Policies.PasswordPolicy"
The PasswordHistorySize parameter (available on Essentials and Plus tiers) prevents users from reusing any of their last N passwords, up to a maximum of 24. Set this to at least 12 to prevent password cycling. The TemporaryPasswordValidityDays controls how long admin-created temporary passwords remain valid -- set this to 1 day to minimize the window for interception.
Cognito threat protection (formerly called advanced security features) provides three critical capabilities: compromised credential detection, adaptive authentication, and detailed user activity logging. Compromised credential detection compares your users' credentials against databases of known leaked passwords and blocks sign-in attempts with exposed credentials. Adaptive authentication assigns a risk score to every authentication attempt and can automatically block or require MFA for high-risk sign-ins.
# Enable threat protection in ENFORCED mode
aws cognito-idp set-user-pool-mfa-config \
--user-pool-id us-east-1_EXAMPLE \
--mfa-configuration ON \
--software-token-mfa-configuration Enabled=true
# Update user pool to enable advanced security
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--user-pool-add-ons AdvancedSecurityMode=ENFORCED
# Verify threat protection is enabled
aws cognito-idp describe-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--query "UserPool.UserPoolAddOns"
Configure adaptive authentication to block high-risk sign-ins and require additional MFA for medium-risk sign-ins. Export threat protection logs to CloudWatch Logs or Amazon S3 for centralized monitoring and alerting. When set to ENFORCED mode, Cognito actively blocks detected threats rather than just logging them.
Security Hub: Control Cognito.1 checks that advanced security features are enabled for user pools.
AWS WAF provides network- and application-layer filtering for your Cognito user pool, protecting against credential stuffing, brute-force attacks, and bot traffic. As of 2025, WAF support extends to Cognito Managed Login endpoints in addition to the classic hosted UI and API endpoints. You can enforce rate limits, geo-restrictions, and IP-based blocking.
# Create a WAF web ACL for Cognito
aws wafv2 create-web-acl \
--name cognito-protection \
--scope REGIONAL \
--default-action Allow={} \
--rules '[
{
"Name": "RateLimitAuth",
"Priority": 1,
"Statement": {
"RateBasedStatement": {
"Limit": 100,
"AggregateKeyType": "IP"
}
},
"Action": {"Block": {}},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimitAuth"
}
}
]' \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=cognito-waf
# Associate the web ACL with your Cognito user pool
aws wafv2 associate-web-acl \
--web-acl-arn arn:aws:wafv2:us-east-1:123456789012:regional/webacl/cognito-protection/EXAMPLE-ID \
--resource-arn arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_EXAMPLE
# Verify association
aws wafv2 get-web-acl-for-resource \
--resource-arn arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_EXAMPLE
Add managed rule groups for additional protection: AWSManagedRulesCommonRuleSet for general threats and AWSManagedRulesBotControlRuleSet for bot detection. WAF integration is available on the Essentials and Plus feature tiers.
Every API that accepts Cognito tokens must perform full JWT verification. A token that passes superficial checks but was tampered with, expired, or issued for a different audience can grant unauthorized access. Signature verification, claim validation, and expiration checks are all required -- skipping any one of them creates an exploitable vulnerability.
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.iss claim matches your user pool URL.token_use -- ensure access tokens are not accepted where ID tokens are expected, and vice versa. Algorithm confusion attacks exploit this gap.aud (for ID tokens) or client_id (for access tokens) matches your app client ID.exp claim) with a small clock skew tolerance (30-60 seconds).kid header as the cache key and refresh periodically.# Fetch the JWKS for your user pool (use this to verify token signatures)
curl -s https://cognito-idp.us-east-1.amazonaws.com/us-east-1_EXAMPLE/.well-known/jwks.json | jq .
# Decode a JWT token (base64 only -- do NOT trust without signature verification)
echo "YOUR_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
Use the aws-jwt-verify library for Node.js applications. It handles key caching, signature verification, and claim validation. For Python, use python-jose or PyJWT with explicit algorithm restrictions. Never accept tokens without specifying the allowed algorithm (RS256 for Cognito) -- leaving this open enables algorithm confusion attacks.
App clients control how your application interacts with the Cognito user pool. Misconfigured app clients are a common source of vulnerabilities -- overly broad OAuth scopes, missing client secrets for server-side applications, and enabled auth flows that are not needed all expand the attack surface.
# Create a secure app client for a server-side application
aws cognito-idp create-user-pool-client \
--user-pool-id us-east-1_EXAMPLE \
--client-name my-secure-app \
--generate-secret \
--explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \
--prevent-user-existence-errors ENABLED \
--token-validity-units '{"AccessToken":"hours","IdToken":"hours","RefreshToken":"days"}' \
--access-token-validity 1 \
--id-token-validity 1 \
--refresh-token-validity 7 \
--allowed-o-auth-flows code \
--allowed-o-auth-scopes openid email profile \
--supported-identity-providers COGNITO
# Audit existing app clients
aws cognito-idp list-user-pool-clients \
--user-pool-id us-east-1_EXAMPLE
# Inspect a specific client
aws cognito-idp describe-user-pool-client \
--user-pool-id us-east-1_EXAMPLE \
--client-id YOUR_CLIENT_ID
Key configuration rules: (1) Always use SRP auth flow (ALLOW_USER_SRP_AUTH) instead of ALLOW_USER_PASSWORD_AUTH, which sends passwords in plaintext. (2) Enable PreventUserExistenceErrors to return generic error messages that do not reveal whether a username exists. (3) Use the authorization code grant (code) for OAuth flows, never the implicit grant. (4) Set short token lifetimes -- 1 hour for access/ID tokens, 7 days for refresh tokens. (5) Store client secrets in AWS Secrets Manager, never in client-side code or public repositories.
Cognito identity pools issue temporary AWS credentials that grant access to AWS resources. The IAM roles assigned to authenticated and unauthenticated users determine what those credentials can do. Overly permissive roles are a direct path to privilege escalation -- an attacker who obtains authenticated credentials with broad permissions can access S3 buckets, DynamoDB tables, or any other AWS resource the role allows.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/private/${cognito-identity.amazonaws.com:sub}/*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/UserData",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
}
}
}
]
}
# Verify identity pool role mappings
aws cognito-identity get-identity-pool-roles \
--identity-pool-id us-east-1:EXAMPLE-GUID
# Check that unauthenticated access is disabled
aws cognito-identity describe-identity-pool \
--identity-pool-id us-east-1:EXAMPLE-GUID \
--query "AllowUnauthenticatedIdentities"
Best practices: (1) Disable unauthenticated (guest) access unless absolutely required. (2) Use the enhanced (simplified) auth flow which applies scope-down policies to limit permissions. (3) Use IAM policy variables like ${cognito-identity.amazonaws.com:sub} to scope resource access to individual users. (4) Apply both cognito-identity.amazonaws.com:aud and cognito-identity.amazonaws.com:amr conditions in IAM trust policies. (5) Use role-based access control with Cognito groups to assign different IAM roles to different user groups.
Cognito Lambda triggers execute custom logic at various points in the authentication lifecycle -- pre-sign-up validation, pre-authentication checks, post-authentication actions, custom message generation, and token generation customization. These triggers run with the permissions of their execution role and process sensitive user data. A vulnerable trigger can leak user information, bypass authentication controls, or escalate privileges.
# List all Lambda triggers configured for a user pool
aws cognito-idp describe-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--query "UserPool.LambdaConfig"
# Ensure trigger Lambda functions have minimal IAM permissions
aws lambda get-function-configuration \
--function-name CognitoPreSignUpTrigger \
--query "Role"
# Review the execution role's policies
aws iam list-attached-role-policies \
--role-name CognitoTriggerRole
Security rules for Lambda triggers: (1) Validate all input -- the event object comes from the user and can contain malicious data. (2) Never log sensitive data -- do not log passwords, tokens, or PII from trigger events to CloudWatch. (3) Apply least privilege to trigger execution roles -- a pre-sign-up trigger does not need access to DynamoDB or S3. (4) Use environment variables with KMS encryption for any secrets the trigger needs. (5) Set concurrency limits to prevent trigger abuse from overwhelming downstream services. (6) Enable threat detection for custom auth flows -- Cognito advanced security now supports risk analysis on custom authentication, adding threat detection to DefineAuthChallenge, CreateAuthChallenge, and VerifyAuthChallenge triggers.
Account recovery is a frequently targeted flow. Attackers who cannot crack a password will attempt to bypass it through password reset. Cognito supports recovery via verified email, verified phone (SMS), or admin-only recovery. The choice of recovery mechanism has direct security implications.
# Set account recovery to verified email only (most secure self-service option)
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--account-recovery-setting '{
"RecoveryMechanisms": [
{
"Priority": 1,
"Name": "verified_email"
}
]
}'
# Require email verification before allowing recovery
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--auto-verified-attributes email
Prefer verified email over SMS for recovery -- SMS is susceptible to SIM-swapping attacks. If your application handles highly sensitive data, consider admin-only recovery where users must contact support and verify their identity through an out-of-band process. Always require email or phone verification before allowing self-service recovery. Monitor recovery requests for anomalous patterns using CloudWatch metrics and threat protection logs.
Public sign-up, if not carefully controlled, can be abused to create fake accounts, inflate SMS costs through verification code abuse, and pollute your user base. Every sign-up attempt that triggers an SMS verification code costs money and can be weaponized in SMS pumping attacks.
# Disable public sign-up if your app uses admin-created accounts
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--admin-create-user-config '{
"AllowAdminCreateUserOnly": true,
"InviteMessageTemplate": {
"EmailSubject": "Your account has been created",
"EmailMessage": "Your username is {username} and temporary password is {####}.",
"SMSMessage": "Your username is {username} and temporary password is {####}."
}
}'
# If public sign-up is required, add a Pre Sign-up Lambda trigger for validation
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--lambda-config '{
"PreSignUp": "arn:aws:lambda:us-east-1:123456789012:function:ValidateSignUp"
}'
If you permit public sign-up: (1) Use a Pre Sign-up Lambda trigger to validate email domains, enforce CAPTCHA, or apply custom registration logic. (2) Configure WAF rate limiting on your user pool to throttle sign-up attempts. (3) Set SMS spend limits in Amazon SNS to prevent cost escalation from verification code abuse. (4) Monitor sign-up metrics in CloudWatch and set alarms for unusual spikes.
Cognito supports multiple authentication flows, and choosing the wrong one can expose credentials in transit. The Secure Remote Password (SRP) protocol is the recommended flow -- it proves the user knows the password without transmitting it over the network. The USER_PASSWORD_AUTH flow, by contrast, sends the password in plaintext (over TLS) and should be avoided.
# Audit which auth flows are enabled on each app client
aws cognito-idp describe-user-pool-client \
--user-pool-id us-east-1_EXAMPLE \
--client-id YOUR_CLIENT_ID \
--query "UserPoolClient.ExplicitAuthFlows"
# Update client to use only SRP and refresh token flows
aws cognito-idp update-user-pool-client \
--user-pool-id us-east-1_EXAMPLE \
--client-id YOUR_CLIENT_ID \
--explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH
For machine-to-machine authentication, use the client credentials grant with OAuth 2.0. Store client secrets in Secrets Manager and rotate them regularly. For passwordless authentication, consider WebAuthn passkeys (supported in Cognito as of 2024), which eliminate the password attack surface entirely. Monitor machine-to-machine authorization with CloudWatch metrics to detect anomalous token request patterns.
You cannot secure what you cannot see. Cognito generates CloudTrail events for administrative API calls, but user authentication events require threat protection logging to be enabled. Without comprehensive logging, you have no visibility into brute-force attempts, compromised credential usage, or anomalous sign-in patterns.
# Verify CloudTrail is capturing Cognito management events
aws cloudtrail get-trail-status --name my-trail
# Enable threat protection logging to CloudWatch Logs
aws cognito-idp set-log-delivery-configuration \
--user-pool-id us-east-1_EXAMPLE \
--log-configurations '[
{
"LogLevel": "ERROR",
"EventSource": "userNotification",
"CloudWatchLogsConfiguration": {
"LogGroupArn": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/cognito/userpools/us-east-1_EXAMPLE:*"
}
}
]'
# Create a CloudWatch alarm for failed authentication attempts
aws cloudwatch put-metric-alarm \
--alarm-name CognitoFailedAuthAlarm \
--metric-name SignInSuccesses \
--namespace AWS/Cognito \
--statistic Sum \
--period 300 \
--threshold 0 \
--comparison-operator LessThanOrEqualToThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:security-alerts
Export threat protection logs to Amazon S3 for long-term retention and analysis. Use Amazon Data Firehose for real-time streaming to SIEM tools. Set up CloudWatch alarms for key metrics: failed sign-in spikes, compromised credential detections, high-risk adaptive authentication events, and unusual geographic patterns. Integrate with AWS Security Hub for centralized security findings across all your Cognito user pools.
These are the most frequently observed Cognito security misconfigurations that we encounter during security assessments:
ALLOW_USER_SRP_AUTH.s3:* or dynamodb:* permissions. Use resource-scoped policies with identity-based variables.| Control | Priority | CLI Verification Command |
|---|---|---|
| MFA Required | Critical | describe-user-pool --query MfaConfiguration |
| Password Min Length >= 14 | High | describe-user-pool --query Policies.PasswordPolicy |
| Threat Protection ENFORCED | Critical | describe-user-pool --query UserPoolAddOns |
| WAF Web ACL Associated | High | wafv2 get-web-acl-for-resource |
| SRP Auth Flow Only | High | describe-user-pool-client --query ExplicitAuthFlows |
| PreventUserExistenceErrors | High | describe-user-pool-client --query PreventUserExistenceErrors |
| Short Token Lifetimes | Medium | describe-user-pool-client --query TokenValidityUnits |
| Guest Access Disabled | High | describe-identity-pool --query AllowUnauthenticatedIdentities |
| Password Reuse Prevention | Medium | describe-user-pool --query Policies.PasswordPolicy.PasswordHistorySize |
| CloudTrail Logging | High | cloudtrail get-trail-status |
| Lambda Trigger Least Privilege | Medium | describe-user-pool --query LambdaConfig |
| Authorization Code Grant (no Implicit) | High | describe-user-pool-client --query AllowedOAuthFlows |
For a quick-reference overview of Cognito security controls, see our Cognito security card. For related security guidance, review our IAM Security Best Practices, WAF Security Best Practices, and Lambda Security Best Practices guides.
This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.
Comprehensive guide to securing AWS Identity and Access Management. Covers MFA enforcement, least privilege, IAM Identity Center, SCPs, Access Analyzer, and credential management.
Comprehensive guide to securing web applications with AWS WAF. Covers managed rules, Bot Control, Fraud Control (ATP/ACFP), CAPTCHA/Challenge actions, rate-based rules, Shield Advanced integration, and centralized WAF management with Firewall Manager.
Comprehensive guide to securing AWS Lambda functions. Covers execution role least privilege, Function URL authentication, VPC placement, code signing, environment variable encryption, Secrets Manager integration, and SnapStart security considerations.