Identity & SecurityIntermediate15 min read

    Amazon Cognito Security Best Practices

    Tarek Cheikh

    Founder & AWS Security Expert

    View Security Card

    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.

    1. Enforce Multi-Factor Authentication (MFA)

    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.

    Implementation

    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.

    2. Configure Strong Password Policies

    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.

    Implementation

    # 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.

    3. Enable Advanced Threat Protection

    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.

    Implementation

    # 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.

    4. Associate AWS WAF with Your User Pool

    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.

    Implementation

    # 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.

    5. Verify JWT Tokens Properly

    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.

    Implementation Checklist

    • Verify the signature using the public keys from the JWKS endpoint at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.
    • Validate the iss claim matches your user pool URL.
    • Check token_use -- ensure access tokens are not accepted where ID tokens are expected, and vice versa. Algorithm confusion attacks exploit this gap.
    • Verify aud (for ID tokens) or client_id (for access tokens) matches your app client ID.
    • Check expiration (exp claim) with a small clock skew tolerance (30-60 seconds).
    • Cache JWKS keys using the 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.

    6. Secure App Client Configuration

    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.

    Implementation

    # 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.

    7. Apply Least Privilege to Identity Pool Roles

    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.

    Implementation

    {
      "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.

    8. Secure Lambda Triggers

    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.

    Implementation

    # 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.

    9. Configure Account Recovery Securely

    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.

    Implementation

    # 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.

    10. Control User Sign-Up and Attribute Verification

    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.

    Implementation

    # 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.

    11. Use Secure Authentication Flows

    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.

    Implementation

    # 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.

    12. Enable Logging and Monitoring

    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.

    Implementation

    # 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.

    Common Misconfigurations

    These are the most frequently observed Cognito security misconfigurations that we encounter during security assessments:

    • USER_PASSWORD_AUTH enabled: Sends passwords over the wire instead of using SRP. Found in the majority of user pools we audit. Switch to ALLOW_USER_SRP_AUTH.
    • MFA set to OFF or OPTIONAL: Optional MFA results in near-zero adoption. Set to REQUIRED for all user pools handling sensitive data.
    • Threat protection in AUDIT mode: Logging threats without blocking them provides visibility but no protection. Switch to ENFORCED mode.
    • PreventUserExistenceErrors disabled: Cognito returns different error messages for "user not found" vs "wrong password," enabling username enumeration. Always enable this setting.
    • Overly broad identity pool IAM roles: Authenticated roles with s3:* or dynamodb:* permissions. Use resource-scoped policies with identity-based variables.
    • No WAF association: User pools exposed directly to the internet without rate limiting or bot detection. Associate a WAF web ACL with rate-based rules.
    • Long token lifetimes: Access tokens valid for 24 hours or more. Set access and ID token validity to 1 hour maximum, refresh tokens to 7 days.
    • Implicit grant flow enabled: The implicit grant returns tokens in URL fragments, making them vulnerable to interception. Use the authorization code grant with PKCE instead.
    • Missing JWT validation: Backend APIs that decode JWT tokens without verifying the signature, issuer, audience, or token_use claim. Use aws-jwt-verify or equivalent libraries.
    • Unverified email/phone used for recovery: Allowing password reset to unverified contact methods. Always require attribute verification before recovery.

    Quick Reference Checklist

    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.

    Go Deeper: The State of AWS Security 2026

    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.

    CognitoMFAAuthenticationJWTUser PoolsIdentity PoolsWAFThreat ProtectionPassword PolicyOAuth