Links#
- What is Amazon Cognito
- Amazon Cognito user pools
- Amazon Cognito identity pools
- User pool managed login
- Authentication flows
- Understanding user pool JSON web tokens
- Security best practices for user pools
- Logging and monitoring in Amazon Cognito
- User pool metrics in CloudWatch
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 API2. 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 string3. 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 roleRecommended for most SaaS/product apps:
why:
AWS credentials stay on backend
backend controls authorization
easier audit and rate limitingsingle-page app calling AWS directly#
browser
-> Cognito User Pool sign-in
-> token
-> Cognito Identity Pool
-> temporary AWS credentials
-> browser calls S3/AppSync/etcUse 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 rolesALB auth#
browser
-> ALB listener rule
-> authenticate-cognito
-> Cognito managed login
-> ALB forwards authenticated request to targetGood for:
admin dashboard
internal tool
legacy app without login
simple path/host protection4. 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-1Production 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 MFA5. 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-1Create 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-1Rules:
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 apps6. 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-1Managed 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=S256Token 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 validNode.js example with jose:
npm install joseimport { 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 database9. 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 operationIAM 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 pathPre 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 blindly11. 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 automationWAF:
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 abuse12. 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
SetIdentityPoolRolesExample 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-1Describe user pool:
aws cognito-idp describe-user-pool \
--user-pool-id ap-east-1_example \
--region ap-east-1Create 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-1Add 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-1List app clients:
aws cognito-idp list-user-pool-clients \
--user-pool-id ap-east-1_example \
--max-results 20 \
--region ap-east-1Global sign-out:
aws cognito-idp admin-user-global-sign-out \
--user-pool-id ap-east-1_example \
--username user@example.com \
--region ap-east-114. 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 launch15. 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