Amazon Cognito
A comprehensive deep dive into Amazon Cognito — User Pools, Identity Pools, JWT tokens, OAuth 2.0 flows, Lambda triggers, federation, hosted UI, and mobile/web authentication patterns for the DVA-C02 exam.
What is Amazon Cognito?
Amazon Cognito is a fully managed identity service for web and mobile applications. It handles user authentication (sign-up, sign-in, MFA) and AWS authorization (exchanging tokens for temporary AWS credentials) so you don't have to build and maintain an identity system from scratch.
Core mental model: Cognito has two halves. User Pools answer "Who are you?" — they authenticate users and issue JWTs. Identity Pools answer "What AWS resources can you access?" — they exchange tokens for temporary AWS credentials.
Part 1 — Cognito User Pools
A User Pool is a fully managed user directory. It stores usernames, passwords, email addresses, phone numbers, and custom attributes. It handles the full authentication lifecycle including sign-up, sign-in, password reset, email/SMS verification, and MFA.
User Pool Features
| Feature | Detail |
|---|---|
| User registration | Self-service sign-up with email/phone verification |
| Sign-in methods | Username, email, phone number |
| Social sign-in | Google, Facebook, Apple, Amazon (via OIDC/SAML federation) |
| MFA | TOTP authenticator app or SMS |
| Password policy | Minimum length, complexity requirements |
| Account recovery | Email or SMS verification code |
| Custom attributes | Up to 25 custom attributes (immutable once set) |
| Lambda triggers | Hook into every step of the auth flow |
| Hosted UI | Pre-built, customizable sign-in page |
| Advanced security | Compromised credential detection, adaptive auth |
Creating a User Pool (SDK)
1import {
2 CognitoIdentityProviderClient,
3 InitiateAuthCommand,
4 SignUpCommand,
5 ConfirmSignUpCommand,
6 ForgotPasswordCommand,
7 ConfirmForgotPasswordCommand,
8} from '@aws-sdk/client-cognito-identity-provider';
9
10const cognito = new CognitoIdentityProviderClient({ region: 'us-east-1' });
11
12const CLIENT_ID = process.env.COGNITO_CLIENT_ID;
13
14// 1. Register a new user
15await cognito.send(new SignUpCommand({
16 ClientId: CLIENT_ID,
17 Username: 'alice@example.com',
18 Password: 'SecurePass123!',
19 UserAttributes: [
20 { Name: 'email', Value: 'alice@example.com' },
21 { Name: 'name', Value: 'Alice Johnson' },
22 { Name: 'custom:tier', Value: 'premium' },
23 ],
24}));
25
26// 2. Confirm registration (code sent to email)
27await cognito.send(new ConfirmSignUpCommand({
28 ClientId: CLIENT_ID,
29 Username: 'alice@example.com',
30 ConfirmationCode: '123456',
31}));
32
33// 3. Sign in — USER_PASSWORD_AUTH flow
34const { AuthenticationResult } = await cognito.send(new InitiateAuthCommand({
35 ClientId: CLIENT_ID,
36 AuthFlow: 'USER_PASSWORD_AUTH',
37 AuthParameters: {
38 USERNAME: 'alice@example.com',
39 PASSWORD: 'SecurePass123!',
40 },
41}));
42
43const { IdToken, AccessToken, RefreshToken } = AuthenticationResult;
44
45// 4. Refresh tokens
46const refreshed = await cognito.send(new InitiateAuthCommand({
47 ClientId: CLIENT_ID,
48 AuthFlow: 'REFRESH_TOKEN_AUTH',
49 AuthParameters: { REFRESH_TOKEN: RefreshToken },
50}));JWT Tokens
Cognito issues three tokens on successful authentication:
| Token | Purpose | Contains | Typical use |
|---|---|---|---|
| ID Token | Proves user identity | User attributes (email, name, custom:tier, sub) | Send to backend to identify the user |
| Access Token | Authorizes API calls | OAuth 2.0 scopes, username, groups | Send as Authorization: Bearer header |
| Refresh Token | Gets new ID + Access tokens | Opaque | Store securely; exchange when Access Token expires |
Token Expiry Defaults
| Token | Default expiry | Range |
|---|---|---|
| ID Token | 1 hour | 5 min – 24 hours |
| Access Token | 1 hour | 5 min – 24 hours |
| Refresh Token | 30 days | 1 hour – 10 years |
Validating a JWT in Your Backend
1import { CognitoJwtVerifier } from 'aws-jwt-verify';
2
3const verifier = CognitoJwtVerifier.create({
4 userPoolId: 'us-east-1_AbCdEfGhI',
5 tokenUse: 'access', // 'id' or 'access'
6 clientId: process.env.COGNITO_CLIENT_ID,
7});
8
9// Express middleware
10async function requireAuth(req, res, next) {
11 try {
12 const token = req.headers.authorization?.replace('Bearer ', '');
13 const payload = await verifier.verify(token);
14
15 req.user = {
16 sub: payload.sub, // unique user ID — use as your userId
17 email: payload.email,
18 tier: payload['custom:tier'],
19 groups: payload['cognito:groups'] ?? [],
20 };
21
22 next();
23 } catch {
24 res.status(401).json({ error: 'Unauthorized' });
25 }
26}Authentication Flows
Cognito supports multiple OAuth 2.0 / custom authentication flows:
| Flow | Use case | How it works |
|---|---|---|
USER_PASSWORD_AUTH | Server-side apps | Username + password sent directly |
USER_SRP_AUTH | Client-side apps | Secure Remote Password — password never sent in plain text |
REFRESH_TOKEN_AUTH | Token renewal | Exchange refresh token for new ID + Access tokens |
CUSTOM_AUTH | Passwordless, OTP | Lambda triggers define custom challenge/response |
ADMIN_USER_PASSWORD_AUTH | Server-side admin | Requires server-side secret; bypasses SRP |
Hosted UI & OAuth 2.0 Endpoints
Cognito provides a pre-built, customizable sign-in page (Hosted UI) with OAuth 2.0 endpoints — no frontend auth code needed.
1# Authorization endpoint — redirect user here to sign in
2GET https://<domain>.auth.us-east-1.amazoncognito.com/oauth2/authorize
3 ?response_type=code
4 &client_id=<CLIENT_ID>
5 &redirect_uri=https://app.example.com/callback
6 &scope=openid+email+profile
7 &state=random-csrf-token
8
9# Token endpoint — exchange authorization code for tokens
10POST https://<domain>.auth.us-east-1.amazoncognito.com/oauth2/token
11 grant_type=authorization_code
12 &code=<AUTH_CODE>
13 &redirect_uri=https://app.example.com/callback
14 &client_id=<CLIENT_ID>
15
16# Logout endpoint
17GET https://<domain>.auth.us-east-1.amazoncognito.com/logout
18 ?client_id=<CLIENT_ID>
19 &logout_uri=https://app.example.comSupported OAuth 2.0 grant types:
- Authorization Code — recommended for web/mobile apps (with PKCE for SPAs)
- Implicit — legacy, deprecated
- Client Credentials — machine-to-machine (no user involved)
Social & Enterprise Federation
Connect external identity providers so users can sign in with their existing accounts:
Users signing in through any provider get a Cognito JWT — your backend always receives the same token format regardless of the sign-in method.
Lambda Triggers
Lambda triggers let you hook into every stage of the User Pool lifecycle to customize behavior:
| Trigger | When it fires | Use case |
|---|---|---|
| Pre sign-up | Before user is created | Block certain email domains, auto-confirm |
| Post confirmation | After user confirms account | Create user record in DynamoDB |
| Pre authentication | Before password is checked | Block sign-in by IP, custom checks |
| Post authentication | After successful sign-in | Log activity, update last-login |
| Pre token generation | Before JWT is issued | Add custom claims to tokens |
| Custom message | Before verification email/SMS is sent | Customize email content |
| User migration | When user signs in but not in pool | Migrate users from legacy system |
| Define auth challenge | Start custom auth flow | Define challenge type (OTP, CAPTCHA) |
| Create auth challenge | Generate the challenge | Send OTP via SES/SNS |
| Verify auth challenge | Validate the response | Check OTP code |
1// Pre Token Generation trigger — add custom claims to the JWT
2exports.handler = async (event) => {
3 // Fetch additional user data from DynamoDB
4 const userTier = await getUserTierFromDB(event.userName);
5
6 event.response = {
7 claimsOverrideDetails: {
8 claimsToAddOrOverride: {
9 'custom:tier': userTier,
10 'custom:featureFlags': 'dark-mode,beta-dashboard',
11 },
12 claimsToSuppress: ['address'], // remove sensitive claims
13 },
14 };
15
16 return event;
17};1// Custom auth flow — passwordless OTP via email
2// 1. Define Auth Challenge
3exports.defineAuthChallenge = async (event) => {
4 event.response.challengeName = 'CUSTOM_CHALLENGE';
5 event.response.issueTokens = false;
6 event.response.failAuthentication = false;
7 return event;
8};
9
10// 2. Create Auth Challenge — send OTP
11exports.createAuthChallenge = async (event) => {
12 const otp = Math.floor(100000 + Math.random() * 900000).toString();
13 await sendOtpEmail(event.request.userAttributes.email, otp);
14
15 event.response.publicChallengeParameters = { destination: event.request.userAttributes.email };
16 event.response.privateChallengeParameters = { answer: otp };
17 return event;
18};
19
20// 3. Verify Auth Challenge — check OTP
21exports.verifyAuthChallenge = async (event) => {
22 event.response.answerCorrect =
23 event.request.challengeAnswer === event.request.privateChallengeParameters.answer;
24 return event;
25};API Gateway & ALB Integration
User Pools integrate natively with API Gateway and ALB — no Lambda authorizer needed for JWT validation.
1// Client sends JWT in Authorization header
2const response = await fetch('https://api.example.com/orders', {
3 headers: {
4 Authorization: idToken, // ID Token for API Gateway Cognito Authorizer
5 // OR
6 Authorization: `Bearer ${accessToken}`, // Access Token for ALB or Lambda authorizer
7 },
8});API Gateway Cognito Authorizer:
- Validates the JWT signature using User Pool's JWKS endpoint
- Checks token expiry and audience (
audclaim) - Passes
requestContext.authorizer.claimsto Lambda (email, sub, custom attributes) - Cache TTL configurable (0–3600s)
Part 2 — Cognito Identity Pools
An Identity Pool (Federated Identities) exchanges an identity token for temporary AWS credentials via STS. This enables mobile and web apps to access AWS services (S3, DynamoDB, Lambda) directly — without routing through your backend.
Authenticated vs Unauthenticated Roles
Identity Pools map identities to IAM roles:
| Role | When used | Example use case |
|---|---|---|
| Authenticated role | User has signed in | Full read/write to their own S3 prefix |
| Unauthenticated role | Guest / not signed in | Read-only access to public content |
1// Authenticated IAM role policy — user can only access their own S3 prefix
2{
3 "Effect": "Allow",
4 "Action": ["s3:GetObject", "s3:PutObject"],
5 "Resource": "arn:aws:s3:::user-uploads/${cognito-identity.amazonaws.com:sub}/*"
6}The ${cognito-identity.amazonaws.com:sub} variable resolves to the user's unique Cognito identity ID — providing automatic per-user data isolation without any backend code.
Getting Credentials from Identity Pool
1import { CognitoIdentityClient, GetIdCommand, GetCredentialsForIdentityCommand } from '@aws-sdk/client-cognito-identity';
2import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
3
4const identityClient = new CognitoIdentityClient({ region: 'us-east-1' });
5
6// 1. Get Identity ID (maps the user to a Cognito Identity)
7const { IdentityId } = await identityClient.send(new GetIdCommand({
8 AccountId: '123456789012',
9 IdentityPoolId: 'us-east-1:abc-123-def',
10 Logins: {
11 'cognito-idp.us-east-1.amazonaws.com/us-east-1_XYZ': idToken, // from User Pool
12 },
13}));
14
15// 2. Exchange for AWS credentials
16const { Credentials } = await identityClient.send(new GetCredentialsForIdentityCommand({
17 IdentityId,
18 Logins: {
19 'cognito-idp.us-east-1.amazonaws.com/us-east-1_XYZ': idToken,
20 },
21}));
22
23// 3. Use temporary credentials directly
24const s3 = new S3Client({
25 region: 'us-east-1',
26 credentials: {
27 accessKeyId: Credentials.AccessKeyId,
28 secretAccessKey: Credentials.SecretAccessKey,
29 sessionToken: Credentials.SessionToken,
30 },
31});
32
33await s3.send(new PutObjectCommand({
34 Bucket: 'user-uploads',
35 Key: `${IdentityId}/profile.jpg`,
36 Body: imageBuffer,
37}));User Pool vs Identity Pool Summary
| Feature | User Pool | Identity Pool |
|---|---|---|
| Purpose | Authentication — who are you? | AWS Authorization — what can you access? |
| Output | JWTs (ID, Access, Refresh tokens) | AWS Temporary Credentials (via STS) |
| Stores users | ✅ Yes — user directory | ❌ No — just maps identities to roles |
| Social sign-in | ✅ Built-in federation | ✅ Accepts any OIDC/SAML token |
| Guest access | ❌ | ✅ Unauthenticated role |
| API Gateway integration | ✅ Native Cognito Authorizer | ❌ (use IAM auth with credentials) |
| Direct AWS access | ❌ (need backend) | ✅ App accesses S3/DynamoDB directly |
Combined Flow (Most Common Pattern)
- User signs in to User Pool → receives JWTs
- App exchanges ID Token with Identity Pool → receives temp AWS credentials
- App uses credentials to access S3/DynamoDB directly (mobile pattern)
- App uses Access Token to call your API Gateway endpoints
DVA-C02 Quick Reference
| Topic | Key Fact |
|---|---|
| User Pool output | JWTs (ID, Access, Refresh tokens) |
| Identity Pool output | Temporary AWS credentials (via STS) |
| ID Token contains | User attributes (email, name, custom claims) |
| Access Token contains | OAuth scopes and groups |
| Default Access Token expiry | 1 hour |
| Default Refresh Token expiry | 30 days |
| Add custom claims to JWT | Pre Token Generation Lambda trigger |
| Migrate users on first sign-in | User Migration Lambda trigger |
| Passwordless OTP auth | Custom Auth Flow (Define/Create/Verify challenge triggers) |
| API Gateway integration | Cognito User Pool Authorizer (validates JWT) |
| Guest / unauthenticated access | Identity Pool unauthenticated role |
| Per-user S3 isolation | IAM policy variable ${cognito-identity.amazonaws.com:sub} |
| Social sign-in | Configured on User Pool (OIDC / SAML federation) |
| SAML / Active Directory | Federated IdP on User Pool OR Identity Pool |
| Hosted UI | Pre-built sign-in page on User Pool with OAuth 2.0 endpoints |
Practice Questions5
Q1. A developer builds a mobile app with user sign-up, sign-in, and MFA. The app needs to authenticate with an AWS API Gateway. Which Amazon Cognito component handles the user directory and authentication?
Select one answer before revealing.
Q2. After signing in via Cognito User Pool, a user wants to call DynamoDB directly from a mobile app without a backend API. Which sequence of steps is correct?
Select one answer before revealing.
Q3. A developer needs to add custom validation logic during Cognito User Pool sign-up — specifically to reject sign-ups from email domains outside the company. Which Cognito feature enables this?
Select one answer before revealing.
Q4. A Cognito User Pool is configured with hosted UI. A user signs in and then calls the token endpoint to exchange the authorization code for tokens. The developer needs to inspect the token to get the user's email and group memberships. Which token contains this information?
Select one answer before revealing.
Q5. A developer is testing a Cognito User Pool integration. The refresh token keeps expiring after 30 days. The developer wants to extend the refresh token expiry. Where is this configured?
Select one answer before revealing.