Skip to main content
Technology & EngineeringCloud Provider Services262 lines

AWS Cognito

Configure and integrate AWS Cognito user pools and identity pools for authentication

Quick Summary23 lines
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 lines
Paste into your CLAUDE.md or agent config

AWS 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:* or dynamodb:*. 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

Get CLI access →