Skip to main content
Technology & EngineeringAws Services286 lines

Cognito

AWS Cognito user authentication and authorization for web and mobile applications

Quick Summary18 lines
You are an expert in Amazon Cognito for implementing authentication, authorization, and user management in web and mobile applications.

## Key Points

- **Skipping token verification on the backend** -- Trusting tokens without signature validation allows forged JWTs to access protected resources. Always verify against the Cognito JWKS.
- **Adding too many custom claims via Lambda triggers** -- ID tokens have a 10,000-character limit. Excessive claims or large group lists overflow the token, causing silent authentication failures.
- **Treating the user pool schema as flexible** -- Custom attributes cannot be removed or have their types changed after creation. Plan thoroughly before creating the pool.
- **Use SRP (Secure Remote Password)** authentication flow for client-side apps. Never send passwords in plain text; let Amplify or the SDK handle SRP.
- **Enable MFA** at minimum as optional, require it for sensitive applications. TOTP is preferred over SMS.
- **Validate tokens on your backend**: Always verify the JWT signature, issuer, audience, and expiration. Cache the JWKS.
- **Use Lambda triggers** for custom logic (validation, enrichment) rather than building it outside the auth flow.
- **Set short access token expiration** (15-60 minutes) and use refresh tokens for long sessions.
- **Use Cognito groups** for role-based access control. Groups appear in the `cognito:groups` claim in the ID token.
- **Configure account recovery** to use verified email or phone, not admin-only recovery.
- **Use a custom domain** for the hosted UI to maintain brand consistency.
- **Client secret with public clients**: SPAs and mobile apps cannot securely store a client secret. Create the app client without `--generate-secret` for public clients.
skilldb get aws-services-skills/CognitoFull skill: 286 lines
Paste into your CLAUDE.md or agent config

AWS Cognito — Cloud Services

You are an expert in Amazon Cognito for implementing authentication, authorization, and user management in web and mobile applications.

Core Philosophy

Authentication is the foundation of trust in any application. Cognito should handle the undifferentiated heavy lifting -- password hashing, token lifecycle, MFA, and social federation -- so your team can focus on business logic. Never roll your own password storage or JWT signing when Cognito provides a standards-compliant, managed alternative that is audited and maintained by AWS.

Design your user pool schema carefully because it cannot be changed later. Custom attributes, case sensitivity settings, and username configuration are immutable after pool creation. Invest time upfront to define what attributes you need, which ones are required versus optional, and whether usernames are emails, phone numbers, or arbitrary strings. Getting this wrong means creating a new user pool and migrating users.

Tokens are the perimeter of your API security. Always verify JWTs on your backend -- check the signature against the JWKS endpoint, validate the issuer, audience, and expiration claims. Cache the JWKS to avoid per-request network calls. Use short-lived access tokens (15-60 minutes) with refresh tokens for long sessions, and leverage Cognito groups for role-based access control rather than building a parallel permission system.

Anti-Patterns

  • Storing or transmitting passwords in application code -- Use SRP (Secure Remote Password) via the Amplify SDK. The password should never cross the wire in plain text or be accessible to your backend.
  • Skipping token verification on the backend -- Trusting tokens without signature validation allows forged JWTs to access protected resources. Always verify against the Cognito JWKS.
  • Using a client secret with public clients -- SPAs and mobile apps cannot securely store secrets. Creating an app client with --generate-secret for a browser app leaks the secret to anyone who inspects the bundle.
  • Adding too many custom claims via Lambda triggers -- ID tokens have a 10,000-character limit. Excessive claims or large group lists overflow the token, causing silent authentication failures.
  • Treating the user pool schema as flexible -- Custom attributes cannot be removed or have their types changed after creation. Plan thoroughly before creating the pool.

Overview

Cognito provides two main components: User Pools for authentication (sign-up, sign-in, token management, MFA) and Identity Pools for authorization (exchanging tokens for temporary AWS credentials). User Pools issue JWTs (ID token, access token, refresh token). Cognito supports OAuth 2.0/OIDC flows, social identity providers (Google, Apple, Facebook), SAML, and custom authentication challenges via Lambda triggers.

Setup & Configuration

Create a User Pool (AWS CLI)

aws cognito-idp create-user-pool \
  --pool-name my-app-users \
  --auto-verified-attributes email \
  --username-attributes email \
  --mfa-configuration OPTIONAL \
  --policies '{
    "PasswordPolicy": {
      "MinimumLength": 12,
      "RequireUppercase": true,
      "RequireLowercase": true,
      "RequireNumbers": true,
      "RequireSymbols": false
    }
  }' \
  --schema '[
    {"Name": "email", "Required": true, "Mutable": true},
    {"Name": "name", "Required": true, "Mutable": true}
  ]'

Create an App Client

aws cognito-idp create-user-pool-client \
  --user-pool-id us-east-1_abc123 \
  --client-name my-app-web \
  --explicit-auth-flows ALLOW_USER_SRP_AUTH ALLOW_REFRESH_TOKEN_AUTH \
  --supported-identity-providers COGNITO \
  --callback-urls '["https://myapp.com/callback"]' \
  --logout-urls '["https://myapp.com/logout"]' \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes openid email profile \
  --allowed-o-auth-flows-user-pool-client \
  --generate-secret  # Omit for public clients (SPAs, mobile)

SDK Setup (JavaScript - Amplify Auth)

import { Amplify } from "aws-amplify";
import { signUp, signIn, signOut, getCurrentUser, fetchAuthSession } from "aws-amplify/auth";

Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: "us-east-1_abc123",
      userPoolClientId: "1a2b3c4d5e6f7g8h9i0j",
      loginWith: {
        oauth: {
          domain: "myapp.auth.us-east-1.amazoncognito.com",
          scopes: ["openid", "email", "profile"],
          redirectSignIn: ["https://myapp.com/callback"],
          redirectSignOut: ["https://myapp.com/logout"],
          responseType: "code",
        },
      },
    },
  },
});

SDK Setup (Python boto3 - Server-Side)

import boto3
import hmac, hashlib, base64

cognito = boto3.client("cognito-idp", region_name="us-east-1")
USER_POOL_ID = "us-east-1_abc123"
CLIENT_ID = "1a2b3c4d5e6f7g8h9i0j"

Core Patterns

Sign Up and Confirm

// Client-side sign up
const { userId } = await signUp({
  username: "alice@example.com",
  password: "SecureP@ss123",
  options: {
    userAttributes: {
      email: "alice@example.com",
      name: "Alice",
    },
  },
});

// Confirm with verification code sent to email
import { confirmSignUp } from "aws-amplify/auth";
await confirmSignUp({
  username: "alice@example.com",
  confirmationCode: "123456",
});
# Server-side sign up (admin)
cognito.admin_create_user(
    UserPoolId=USER_POOL_ID,
    Username="alice@example.com",
    UserAttributes=[
        {"Name": "email", "Value": "alice@example.com"},
        {"Name": "email_verified", "Value": "true"},
        {"Name": "name", "Value": "Alice"},
    ],
    TemporaryPassword="TempP@ss123",
)

Sign In

const { isSignedIn, nextStep } = await signIn({
  username: "alice@example.com",
  password: "SecureP@ss123",
});

if (nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_TOTP_CODE") {
  // MFA required
  import { confirmSignIn } from "aws-amplify/auth";
  await confirmSignIn({ challengeResponse: "123456" });
}
# Server-side authentication
response = cognito.admin_initiate_auth(
    UserPoolId=USER_POOL_ID,
    ClientId=CLIENT_ID,
    AuthFlow="ADMIN_USER_PASSWORD_AUTH",
    AuthParameters={
        "USERNAME": "alice@example.com",
        "PASSWORD": "SecureP@ss123",
    },
)
id_token = response["AuthenticationResult"]["IdToken"]
access_token = response["AuthenticationResult"]["AccessToken"]
refresh_token = response["AuthenticationResult"]["RefreshToken"]

Verify JWT Tokens (Backend API)

import jwt
import requests

# Fetch JWKS (cache this)
JWKS_URL = f"https://cognito-idp.us-east-1.amazonaws.com/{USER_POOL_ID}/.well-known/jwks.json"
jwks = requests.get(JWKS_URL).json()

def verify_token(token):
    # Decode header to get key ID
    header = jwt.get_unverified_header(token)
    key = next(k for k in jwks["keys"] if k["kid"] == header["kid"])

    # Verify and decode
    payload = jwt.decode(
        token,
        jwt.algorithms.RSAAlgorithm.from_jwk(key),
        algorithms=["RS256"],
        audience=CLIENT_ID,  # Use "aud" for id_token, "client_id" claim for access_token
        issuer=f"https://cognito-idp.us-east-1.amazonaws.com/{USER_POOL_ID}",
    )
    return payload

Lambda Triggers

# Pre-sign-up trigger: auto-confirm users from a specific domain
aws cognito-idp update-user-pool \
  --user-pool-id us-east-1_abc123 \
  --lambda-config '{
    "PreSignUp": "arn:aws:lambda:us-east-1:123456789012:function:pre-signup-validate",
    "PostConfirmation": "arn:aws:lambda:us-east-1:123456789012:function:post-confirm-welcome",
    "PreTokenGeneration": "arn:aws:lambda:us-east-1:123456789012:function:pre-token-add-claims"
  }'
# Pre-sign-up Lambda: auto-confirm corporate emails
def handler(event, context):
    email = event["request"]["userAttributes"].get("email", "")
    if email.endswith("@mycompany.com"):
        event["response"]["autoConfirmUser"] = True
        event["response"]["autoVerifyEmail"] = True
    return event

# Pre-token-generation Lambda: add custom claims
def handler(event, context):
    event["response"]["claimsOverrideDetails"] = {
        "claimsToAddOrOverride": {
            "custom:role": "admin",
            "custom:tenant_id": "tenant-001",
        }
    }
    return event

Cognito with API Gateway Authorizer

# CloudFormation
CognitoAuthorizer:
  Type: AWS::ApiGateway::Authorizer
  Properties:
    Name: CognitoAuth
    Type: COGNITO_USER_POOLS
    RestApiId: !Ref Api
    IdentitySource: method.request.header.Authorization
    ProviderARNs:
      - !GetAtt UserPool.Arn

Groups and Role-Based Access

aws cognito-idp create-group \
  --user-pool-id us-east-1_abc123 \
  --group-name admins \
  --description "Administrator users" \
  --role-arn arn:aws:iam::123456789012:role/AdminRole

aws cognito-idp admin-add-user-to-group \
  --user-pool-id us-east-1_abc123 \
  --username alice@example.com \
  --group-name admins

Best Practices

  • Use SRP (Secure Remote Password) authentication flow for client-side apps. Never send passwords in plain text; let Amplify or the SDK handle SRP.
  • Enable MFA at minimum as optional, require it for sensitive applications. TOTP is preferred over SMS.
  • Validate tokens on your backend: Always verify the JWT signature, issuer, audience, and expiration. Cache the JWKS.
  • Use Lambda triggers for custom logic (validation, enrichment) rather than building it outside the auth flow.
  • Set short access token expiration (15-60 minutes) and use refresh tokens for long sessions.
  • Use Cognito groups for role-based access control. Groups appear in the cognito:groups claim in the ID token.
  • Configure account recovery to use verified email or phone, not admin-only recovery.
  • Use a custom domain for the hosted UI to maintain brand consistency.

Common Pitfalls

  • Client secret with public clients: SPAs and mobile apps cannot securely store a client secret. Create the app client without --generate-secret for public clients.
  • User pool schema is immutable: Custom attributes cannot be removed or have their types changed after creation. Plan the schema carefully upfront.
  • Token size limits: ID tokens have a 10,000-character limit. Adding too many custom claims or groups can exceed this.
  • Refresh token rotation: Cognito does not rotate refresh tokens by default. Enable refresh token rotation for better security.
  • Case sensitivity: By default, usernames are case-sensitive. Use UsernameConfiguration: CaseSensitive: false at pool creation time (cannot change later).
  • Rate limits: Cognito has request rate limits (e.g., 50 RPS for AdminInitiateAuth by default). Request quota increases for production traffic.
  • Hosted UI limitations: The Cognito hosted UI has limited customization. For full control, build a custom UI using Amplify Auth SDK and handle the flows yourself.

Install this skill directly: skilldb add aws-services-skills

Get CLI access →