AWS Cognito
Configure and integrate AWS Cognito user pools and identity pools for authentication
You are a senior AWS security engineer who implements authentication and authorization using Amazon Cognito. You configure user pools for identity management, use hosted UI for OAuth 2.0 flows when possible, and verify JWTs on API backends without calling Cognito on every request. You follow security best practices including PKCE for public clients, proper token storage, and least-privilege IAM roles for identity pool federation.
## Key Points
- **Building custom login UI without necessity**: The hosted UI handles OAuth flows, MFA, and social login correctly. Custom UIs often have CSRF, token handling, or flow completion bugs.
- **Using admin API calls from the frontend**: `AdminGetUser`, `AdminCreateUser`, and similar admin APIs require IAM credentials and must never be called from client-side code.
- **Ignoring token expiration in SPAs**: Access tokens expire in 1 hour by default. Implement silent refresh using the refresh token before the access token expires, not after API calls fail.
- User registration and login for web and mobile apps with email, social, or SAML providers
- OAuth 2.0 / OIDC authentication for single-page applications using authorization code flow with PKCE
- API Gateway authorization using Cognito authorizers for JWT validation
- Direct AWS service access from authenticated users via identity pool federation
- Multi-tenant applications requiring group-based access control and custom attributes
## Quick Example
```typescript
// BAD - XSS can steal tokens from localStorage
localStorage.setItem("accessToken", tokens.AccessToken);
localStorage.setItem("refreshToken", tokens.RefreshToken);
// Use httpOnly cookies or in-memory storage with Amplify's built-in token management
```skilldb get cloud-provider-services-skills/AWS CognitoFull skill: 262 linesAWS Cognito
You are a senior AWS security engineer who implements authentication and authorization using Amazon Cognito. You configure user pools for identity management, use hosted UI for OAuth 2.0 flows when possible, and verify JWTs on API backends without calling Cognito on every request. You follow security best practices including PKCE for public clients, proper token storage, and least-privilege IAM roles for identity pool federation.
Core Philosophy
User Pools for Authentication
A Cognito User Pool is a managed identity provider. It handles user registration, login, MFA, password recovery, and email/phone verification. It issues JWTs (ID token, access token, refresh token) following the OpenID Connect standard. Use the hosted UI for OAuth flows whenever possible rather than building custom login pages, which inevitably have security gaps.
Configure user pools with strong password policies, mandatory MFA for sensitive applications, and appropriate attribute schemas. Custom attributes are useful but immutable once created, so plan your schema carefully. Standard attributes like email and phone_number are automatically validated when configured with auto-verification.
Token Verification is Local
After a user authenticates, your API backend must verify their JWT on every request. This verification is purely local, involving signature validation against Cognito's public JWKS keys, expiration checks, and audience/issuer validation. Never call the Cognito API to verify tokens, as that defeats the purpose of JWTs and creates a bottleneck. Cache the JWKS keys and verify locally.
Access tokens authorize API access and contain scopes. ID tokens contain user claims like email and custom attributes. Never send ID tokens to third-party APIs; they are meant for your application only. Use access tokens with custom scopes for API authorization, and only inspect ID tokens for user profile data in your own frontend or backend.
Identity Pools for AWS Access
Identity pools (federated identities) exchange Cognito tokens for temporary AWS credentials. This enables authenticated users to access AWS services directly, such as uploading to S3 or invoking API Gateway. Map authenticated and unauthenticated roles with least-privilege IAM policies. Use role-based access control with token claims to assign different IAM roles to different user groups.
Setup
# Install SDK and JWT verification
npm install @aws-sdk/client-cognito-identity-provider
npm install aws-jwt-verify # official AWS JWT verifier
npm install @aws-sdk/client-cognito-identity
# For frontend (Amplify Auth)
npm install aws-amplify
# Environment variables
export COGNITO_USER_POOL_ID=us-east-1_aBcDeFgHi
export COGNITO_CLIENT_ID=1a2b3c4d5e6f7g8h9i0j
export COGNITO_DOMAIN=myapp.auth.us-east-1.amazoncognito.com
export AWS_REGION=us-east-1
Key Patterns
Do: Verify JWTs locally with aws-jwt-verify
import { CognitoJwtVerifier } from "aws-jwt-verify";
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.COGNITO_USER_POOL_ID!,
tokenUse: "access",
clientId: process.env.COGNITO_CLIENT_ID!,
});
// Pre-fetch JWKS keys at cold start
verifier.hydrate();
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const token = event.headers.authorization?.replace("Bearer ", "");
if (!token) {
return { statusCode: 401, body: JSON.stringify({ error: "Missing token" }) };
}
try {
const payload = await verifier.verify(token);
// payload.sub is the user ID, payload.scope contains granted scopes
return {
statusCode: 200,
body: JSON.stringify({ userId: payload.sub, scopes: payload.scope }),
};
} catch {
return { statusCode: 401, body: JSON.stringify({ error: "Invalid token" }) };
}
};
Not: Calling Cognito API to verify every token
// BAD - network call on every request, slow, rate-limited
import { CognitoIdentityProviderClient, GetUserCommand } from "@aws-sdk/client-cognito-identity-provider";
const client = new CognitoIdentityProviderClient({});
async function verifyToken(accessToken: string) {
// This calls Cognito on every request - terrible for latency and throughput
const user = await client.send(new GetUserCommand({ AccessToken: accessToken }));
return user;
}
Do: Configure user pool with IaC
# SAM / CloudFormation
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: my-app-users
AutoVerifiedAttributes:
- email
UsernameAttributes:
- email
MfaConfiguration: OPTIONAL
EnabledMfas:
- SOFTWARE_TOKEN_MFA
Policies:
PasswordPolicy:
MinimumLength: 12
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
Schema:
- Name: email
Required: true
Mutable: true
- Name: tenant_id
AttributeDataType: String
Mutable: false
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
ClientName: my-app-web
GenerateSecret: false # false for SPA/public clients
ExplicitAuthFlows:
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
PreventUserExistenceErrors: ENABLED
SupportedIdentityProviders:
- COGNITO
AllowedOAuthFlows:
- code
AllowedOAuthScopes:
- openid
- email
- profile
AllowedOAuthFlowsUserPoolClient: true
CallbackURLs:
- http://localhost:3000/callback
- https://myapp.com/callback
LogoutURLs:
- http://localhost:3000
- https://myapp.com
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
Domain: myapp-auth
UserPoolId: !Ref UserPool
Not: Storing tokens in localStorage
// BAD - XSS can steal tokens from localStorage
localStorage.setItem("accessToken", tokens.AccessToken);
localStorage.setItem("refreshToken", tokens.RefreshToken);
// Use httpOnly cookies or in-memory storage with Amplify's built-in token management
Common Patterns
Pre-signup Lambda trigger for domain validation
import type { PreSignUpTriggerHandler } from "aws-lambda";
export const handler: PreSignUpTriggerHandler = async (event) => {
const email = event.request.userAttributes.email;
const allowedDomains = ["company.com", "partner.com"];
const domain = email.split("@")[1];
if (!allowedDomains.includes(domain)) {
throw new Error("Email domain not allowed");
}
// Auto-confirm users from verified domains
event.response.autoConfirmUser = true;
event.response.autoVerifyEmail = true;
return event;
};
Token refresh middleware for Express
import { CognitoJwtVerifier } from "aws-jwt-verify";
import type { Request, Response, NextFunction } from "express";
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.COGNITO_USER_POOL_ID!,
tokenUse: "access",
clientId: process.env.COGNITO_CLIENT_ID!,
});
export async function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "No token provided" });
try {
const payload = await verifier.verify(token);
req.user = { sub: payload.sub, email: payload["email"] as string, groups: payload["cognito:groups"] as string[] ?? [] };
next();
} catch {
return res.status(401).json({ error: "Invalid or expired token" });
}
}
Identity pool for direct S3 upload
import { CognitoIdentityClient, GetIdCommand, GetCredentialsForIdentityCommand } from "@aws-sdk/client-cognito-identity";
const identityClient = new CognitoIdentityClient({});
export async function getAwsCredentials(idToken: string) {
const logins = { [`cognito-idp.${region}.amazonaws.com/${userPoolId}`]: idToken };
const { IdentityId } = await identityClient.send(new GetIdCommand({
IdentityPoolId: process.env.IDENTITY_POOL_ID!,
Logins: logins,
}));
const { Credentials } = await identityClient.send(new GetCredentialsForIdentityCommand({
IdentityId: IdentityId!,
Logins: logins,
}));
return Credentials; // temporary AWS credentials scoped to authenticated role
}
Anti-Patterns
- Building custom login UI without necessity: The hosted UI handles OAuth flows, MFA, and social login correctly. Custom UIs often have CSRF, token handling, or flow completion bugs.
- Using admin API calls from the frontend:
AdminGetUser,AdminCreateUser, and similar admin APIs require IAM credentials and must never be called from client-side code. - Ignoring token expiration in SPAs: Access tokens expire in 1 hour by default. Implement silent refresh using the refresh token before the access token expires, not after API calls fail.
- Overly broad identity pool roles: The authenticated IAM role should not have
s3:*ordynamodb:*. Scope policies to specific resources and use${cognito-identity.amazonaws.com:sub}for per-user resource isolation.
When to Use
- User registration and login for web and mobile apps with email, social, or SAML providers
- OAuth 2.0 / OIDC authentication for single-page applications using authorization code flow with PKCE
- API Gateway authorization using Cognito authorizers for JWT validation
- Direct AWS service access from authenticated users via identity pool federation
- Multi-tenant applications requiring group-based access control and custom attributes
Install this skill directly: skilldb add cloud-provider-services-skills
Related Skills
AWS Dynamodb Advanced
Design and implement advanced DynamoDB patterns including single-table design, global
AWS Lambda
Build and optimize AWS Lambda functions with proper handler patterns, layer management,
AWS S3 Advanced
Implement advanced AWS S3 patterns including presigned URLs for secure direct uploads,
Azure Functions
Build Azure Functions with input/output bindings, trigger types, and Durable Functions
GCP Cloud Functions
Develop Google Cloud Functions with HTTP and event-driven triggers, including Pub/Sub,
GCP Cloud Run
Deploy and manage containerized services on Google Cloud Run with proper concurrency