AWS Cognito


1. Important Points#

Amazon Cognito 是 AWS 托管的 authentication / authorization 服务。最常用的是 User Pool,它可以作为 OIDC IdP 给 Web、mobile、API、ALB Auth 使用。Identity Pool 是另一类能力,用来把已经认证的用户换成临时 AWS credentials。

Cognito User Pool 用来做:
    user directory
    sign-up / sign-in
    managed login / hosted UI
    OAuth 2.0 / OIDC provider
    JWT token issuer
    social / OIDC / SAML federation
    MFA / passkey / password policy
    Lambda trigger customization
    app client and resource server scopes

Cognito Identity Pool 用来做:
    exchange external/user-pool identity for temporary AWS credentials
    allow app users to access AWS resources directly with IAM role
    guest / unauthenticated identity, if explicitly enabled

Cognito 不适合:
    complex fine-grained business authorization by itself
    full IAM replacement
    custom enterprise IAM governance
    storing application profile data as primary database

核心原则:

browser / mobile app:
    use Authorization Code + PKCE
    do not use implicit flow
    do not store tokens in localStorage

backend API:
    verify JWT signature, issuer, audience/client_id, token_use, exp
    authorize with app roles/scopes after token validation

machine-to-machine:
    use client credentials grant with confidential app client
    use custom scopes and resource server

AWS resource access from app:
    use Identity Pool only when app needs direct AWS credentials
    otherwise keep AWS access behind backend API

2. Core Concepts#

Concept Meaning Production Note
User pool 用户目录和 OIDC IdP most common Cognito component
App client application registration one per web/mobile/backend client type
Managed login Cognito-hosted auth pages and OAuth endpoints fastest safe login UI
Domain Cognito auth domain or custom domain required for OAuth/OIDC hosted flow
IdP Google/Apple/OIDC/SAML federation source map attributes carefully
Resource server custom API identifier and scopes useful for M2M / API authorization
Identity pool temporary AWS credential broker use only when client needs AWS APIs
Group user pool group can appear in tokens and map to roles
Lambda trigger customize auth lifecycle useful but increases operational complexity

Token types:

Token Purpose Common Consumer
ID token user identity claims frontend / app session
Access token API authorization claims and scopes backend API / ALB / resource server
Refresh token get new ID/access tokens client session renewal
do not confuse:
    user pool group:
        identity / app authorization metadata

    IAM role:
        AWS API permission

    OAuth scope:
        delegated API permission string

3. Architecture Patterns#

web app with backend api#

browser
    -> Cognito managed login
        -> authorization code + PKCE
            -> browser receives tokens
                -> API call with Authorization: Bearer <access_token>
                    -> backend verifies JWT
                        -> backend calls AWS services with its own IAM role

Recommended for most SaaS/product apps:

why:
    AWS credentials stay on backend
    backend controls authorization
    easier audit and rate limiting

single-page app calling AWS directly#

browser
    -> Cognito User Pool sign-in
        -> token
            -> Cognito Identity Pool
                -> temporary AWS credentials
                    -> browser calls S3/AppSync/etc

Use only when direct AWS access is part of the design:

examples:
    upload to user's S3 prefix
    AppSync with IAM auth
    IoT / mobile app AWS SDK access

risk:
    IAM role policy must be very strict
    never grant broad AWS permissions to identity pool roles

ALB auth#

browser
    -> ALB listener rule
        -> authenticate-cognito
            -> Cognito managed login
                -> ALB forwards authenticated request to target

Good for:

admin dashboard
internal tool
legacy app without login
simple path/host protection

4. User Pool Configuration#

Create user pool with email sign-in and MFA optional:

cat > user-pool.json <<'EOF'
{
  "PoolName": "prod-order-user-pool",
  "UsernameAttributes": ["email"],
  "AutoVerifiedAttributes": ["email"],
  "MfaConfiguration": "OPTIONAL",
  "Policies": {
    "PasswordPolicy": {
      "MinimumLength": 12,
      "RequireUppercase": true,
      "RequireLowercase": true,
      "RequireNumbers": true,
      "RequireSymbols": true,
      "TemporaryPasswordValidityDays": 7
    }
  },
  "AccountRecoverySetting": {
    "RecoveryMechanisms": [
      {
        "Priority": 1,
        "Name": "verified_email"
      }
    ]
  },
  "AdminCreateUserConfig": {
    "AllowAdminCreateUserOnly": false
  },
  "UserPoolTags": {
    "env": "prod",
    "service": "order-api"
  }
}
EOF

aws cognito-idp create-user-pool \
  --cli-input-json file://user-pool.json \
  --region ap-east-1

Production defaults:

sign-in:
    email sign-in is simpler for user experience
    avoid mutable username dependency in app database

self-registration:
    enable only for public product sign-up
    disable for enterprise/internal-only user pools

MFA:
    required for admin/internal users
    optional or adaptive for consumer apps based on UX/risk

password:
    use passkey/passwordless where possible
    if using password, enforce strong policy and MFA

5. App Client#

Create public app client for browser/mobile with Authorization Code + PKCE:

export USER_POOL_ID="ap-east-1_example"

aws cognito-idp create-user-pool-client \
  --user-pool-id "$USER_POOL_ID" \
  --client-name order-web \
  --generate-secret false \
  --supported-identity-providers COGNITO \
  --callback-urls https://app.example.com/auth/callback http://localhost:3000/auth/callback \
  --logout-urls https://app.example.com/logout http://localhost:3000/logout \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes openid email profile \
  --allowed-o-auth-flows-user-pool-client \
  --prevent-user-existence-errors ENABLED \
  --access-token-validity 60 \
  --id-token-validity 60 \
  --refresh-token-validity 30 \
  --token-validity-units AccessToken=minutes,IdToken=minutes,RefreshToken=days \
  --region ap-east-1

Create confidential client for machine-to-machine:

aws cognito-idp create-user-pool-client \
  --user-pool-id "$USER_POOL_ID" \
  --client-name order-worker-m2m \
  --generate-secret \
  --allowed-o-auth-flows client_credentials \
  --allowed-o-auth-scopes orders/read orders/write \
  --allowed-o-auth-flows-user-pool-client \
  --access-token-validity 15 \
  --token-validity-units AccessToken=minutes \
  --region ap-east-1

Rules:

public client:
    no client secret
    use Authorization Code + PKCE
    browser / mobile / SPA

confidential client:
    has client secret
    backend / worker only
    store secret in Secrets Manager or CI secret store

avoid:
    implicit flow for new apps
    long-lived access tokens
    sharing one app client across unrelated apps

6. Managed Login#

Create Cognito prefix domain:

aws cognito-idp create-user-pool-domain \
  --user-pool-id "$USER_POOL_ID" \
  --domain order-auth-prod \
  --managed-login-version 2 \
  --region ap-east-1

Managed login endpoints:

authorize:
    https://order-auth-prod.auth.ap-east-1.amazoncognito.com/oauth2/authorize

token:
    https://order-auth-prod.auth.ap-east-1.amazoncognito.com/oauth2/token

logout:
    https://order-auth-prod.auth.ap-east-1.amazoncognito.com/logout

jwks:
    https://cognito-idp.ap-east-1.amazonaws.com/<user_pool_id>/.well-known/jwks.json

issuer:
    https://cognito-idp.ap-east-1.amazonaws.com/<user_pool_id>

Authorization request:

https://order-auth-prod.auth.ap-east-1.amazoncognito.com/oauth2/authorize
  ?client_id=<app_client_id>
  &response_type=code
  &scope=openid+email+profile
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback
  &code_challenge=<pkce_code_challenge>
  &code_challenge_method=S256

Token request:

curl -X POST "https://order-auth-prod.auth.ap-east-1.amazoncognito.com/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=<app_client_id>" \
  -d "code=<authorization_code>" \
  -d "redirect_uri=https://app.example.com/auth/callback" \
  -d "code_verifier=<pkce_code_verifier>"

7. OAuth Flows#

Flow Use Case Recommendation
Authorization Code + PKCE browser/mobile user login default for user-facing apps
Client Credentials machine-to-machine backend/worker only
SRP custom UI with Cognito native auth useful, more app logic
Custom Auth Challenge passwordless/custom challenge advanced
Implicit legacy browser apps avoid for new apps

Client credentials example:

CLIENT_ID="<confidential_client_id>"
CLIENT_SECRET="<confidential_client_secret>"

curl -X POST "https://order-auth-prod.auth.ap-east-1.amazoncognito.com/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "${CLIENT_ID}:${CLIENT_SECRET}" \
  -d "grant_type=client_credentials" \
  -d "scope=orders/read"

Expected output:

{
  "access_token": "eyJraWQiOiJ...",
  "expires_in": 900,
  "token_type": "Bearer"
}

8. Token Verification#

Backend must verify tokens locally before trusting claims.

verify:
    signature with JWKS
    issuer = https://cognito-idp.<region>.amazonaws.com/<user_pool_id>
    exp / nbf / iat
    token_use = access or id
    client_id / aud as appropriate
    scope for API authorization
    groups/roles only after token is valid

Node.js example with jose:

npm install jose
import { createRemoteJWKSet, jwtVerify } from "jose";

const region = "ap-east-1";
const userPoolId = "ap-east-1_example";
const issuer = `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`;
const jwks = createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`));

export async function verifyAccessToken(token, clientId) {
  const { payload } = await jwtVerify(token, jwks, {
    issuer
  });

  if (payload.token_use !== "access") {
    throw new Error("invalid token_use");
  }

  if (payload.client_id !== clientId) {
    throw new Error("invalid client_id");
  }

  return payload;
}

Authorization rule:

authentication:
    token is valid and not expired

authorization:
    token has required scope / group / app role
    app checks resource ownership in database

9. Identity Pool#

Use Identity Pool when the client app needs temporary AWS credentials.

common use:
    upload file directly to S3 under user's prefix
    call AppSync with IAM auth
    mobile app calls AWS service directly

avoid:
    giving broad S3/DynamoDB permissions to browser
    using identity pool when backend API can do the AWS operation

IAM role trust policy for authenticated identities:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "ap-east-1:identity-pool-id"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

Least-privilege S3 prefix policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "UserPrefixAccess",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::company-prod-user-files/${cognito-identity.amazonaws.com:sub}/*"
    }
  ]
}

10. Lambda Triggers#

Lambda triggers customize Cognito workflows.

Trigger Use Case
Pre sign-up block/auto-confirm based on domain or invite
Pre authentication custom risk check before auth
Post authentication audit login event
Pre token generation add custom claims
Custom auth challenge passwordless/custom challenge
User migration migrate user on first login
Custom message customize email/SMS

Guidance:

use triggers carefully:
    auth path becomes dependent on Lambda availability and latency
    add timeout / retry awareness
    log enough context for incident
    do not put heavy business workflow in sign-in path

Pre token generation use:

good:
    add app_role claim from trusted source
    normalize group claim
    add tenant_id claim after validation

bad:
    call many downstream services
    add large claims
    trust client-provided attributes blindly

11. Security Best Practices#

tokens:
    do not store ID/access tokens in localStorage
    prefer httpOnly secure cookies for browser session where architecture allows
    rotate/revoke refresh tokens on sign-out or compromise
    validate JWT on every backend request

app client:
    public client has no secret
    confidential client secret stays server-side only
    separate app clients by platform and trust boundary

sign-up:
    disable self-registration for internal apps
    protect public sign-up with WAF/rate controls when abused
    verify email/phone before trusting contact attribute

MFA:
    require for admin/internal app
    use adaptive/threat protection if available and appropriate

federation:
    trust only required IdPs
    map attributes explicitly
    avoid using mutable email as sole authorization identity

IAM:
    least privilege for admin APIs
    never call admin APIs from browser with long-lived AWS keys
    use temporary AWS credentials for backend/admin automation

WAF:

Cognito user pools can be protected with AWS WAF:
    reduce bot sign-up
    reduce credential stuffing
    block known bad IPs
    rate limit auth endpoints

monitor:
    WAF blocked request spike
    sign-in failure spike
    sign-up abuse

12. Monitoring#

Cognito monitoring sources:

Source Purpose
CloudWatch Metrics aggregate user pool activity and quotas
CloudWatch Logs detailed user activity logs when configured
CloudTrail Cognito API calls and management activity
WAF logs blocked/allowed auth endpoint traffic
App logs token verification failures and auth decisions

Must-have alerts:

Severity Alert Meaning Why Monitor Signal
P1 sign-in failure spike 登录失败数量异常升高 发现凭证攻击、IdP 故障、回调配置错误或发布问题 app logs / Cognito logs / CloudWatch metric
P1 quota near limit Cognito 或相关 AWS Usage 接近配额 防止登录、注册、M2M token 请求被限流 AWS/Usage quota usage
P1 WAF blocked auth spike WAF 在认证入口阻止大量请求 发现 credential stuffing、bot、扫描或误拦截 AWS/WAFV2 BlockedRequests
P2 SMS/email delivery failure 验证码、邀请或恢复邮件/短信发送失败 用户无法注册、登录恢复或完成 MFA user activity logs / app complaint
P2 admin API change 用户池、client、group 等管理操作发生变化 发现误操作、越权修改或安全配置漂移 CloudTrail event
P2 token verification failure spike 后端 JWT 验证失败数量升高 发现错误 token、错误 user pool、过期 token 或攻击尝试 backend app metric

CloudTrail events to watch:

cognito-idp:
    CreateUserPool
    DeleteUserPool
    UpdateUserPool
    CreateUserPoolClient
    UpdateUserPoolClient
    CreateUserPoolDomain
    AdminCreateUser
    AdminDeleteUser
    AdminAddUserToGroup
    AdminRemoveUserFromGroup
    SetRiskConfiguration

cognito-identity:
    CreateIdentityPool
    UpdateIdentityPool
    SetIdentityPoolRoles

Example EventBridge pattern:

{
  "source": ["aws.cognito-idp"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["cognito-idp.amazonaws.com"],
    "eventName": [
      "DeleteUserPool",
      "UpdateUserPool",
      "UpdateUserPoolClient",
      "SetRiskConfiguration",
      "AdminAddUserToGroup",
      "AdminRemoveUserFromGroup"
    ]
  }
}

13. Hands-on#

List user pools:

aws cognito-idp list-user-pools \
  --max-results 20 \
  --region ap-east-1

Describe user pool:

aws cognito-idp describe-user-pool \
  --user-pool-id ap-east-1_example \
  --region ap-east-1

Create user:

aws cognito-idp admin-create-user \
  --user-pool-id ap-east-1_example \
  --username user@example.com \
  --user-attributes Name=email,Value=user@example.com Name=email_verified,Value=true \
  --temporary-password 'ChangeMe-123456789!' \
  --region ap-east-1

Add user to group:

aws cognito-idp create-group \
  --user-pool-id ap-east-1_example \
  --group-name admin \
  --description "Application administrators" \
  --region ap-east-1

aws cognito-idp admin-add-user-to-group \
  --user-pool-id ap-east-1_example \
  --username user@example.com \
  --group-name admin \
  --region ap-east-1

List app clients:

aws cognito-idp list-user-pool-clients \
  --user-pool-id ap-east-1_example \
  --max-results 20 \
  --region ap-east-1

Global sign-out:

aws cognito-idp admin-user-global-sign-out \
  --user-pool-id ap-east-1_example \
  --username user@example.com \
  --region ap-east-1

14. Production Checklist#

design:
    User Pool vs Identity Pool decision documented
    auth flow selected per client type
    app clients separated by platform/trust boundary
    callback/logout URLs reviewed

security:
    Authorization Code + PKCE for browser/mobile
    no implicit flow for new apps
    public clients have no client secret
    confidential client secrets stored securely
    MFA policy defined
    token verification implemented on backend
    WAF considered for public auth endpoints

operations:
    CloudTrail monitored for pool/client/group changes
    sign-in failure and quota alarms exist
    user lifecycle runbook exists
    break-glass admin process documented
    refresh-token revocation process documented

identity data:
    app database stores stable subject id
    mutable email is not primary authorization key
    custom attributes are minimized
    profile data is stored in app database when business-owned

cost / quota:
    MAU and M2M usage monitored
    SMS/email sending cost monitored
    service quotas reviewed before launch

15. Common Mistakes#

mistake:
    use email as permanent user id
result:
    email change or federation mapping breaks identity

mistake:
    store access token in localStorage
result:
    XSS can steal token

mistake:
    backend trusts JWT without verifying signature and issuer
result:
    forged or wrong-pool token can be accepted

mistake:
    use one app client for SPA, mobile, backend, and worker
result:
    token lifetime, redirect URL, and secret handling become unsafe

mistake:
    give identity pool role broad S3/DynamoDB access
result:
    compromised client token can access too much data

mistake:
    put heavy Lambda trigger logic in sign-in path
result:
    auth latency and availability become unstable