Home β€Ί AWS Resources β€Ί Amazon Cognito

Amazon Cognito

Managed authentication on AWS: user pools, app clients, JWT validation, hosted UI, and when Cognito is (and isn't) the right choice.

What Is Cognito?

Cognito is AWS's managed authentication service. It handles user registration, login, password reset, MFA, social sign-in (Google, Apple, Facebook), and SAML/OIDC federation. You get a user directory and JWT token issuance without building auth from scratch.

It competes with Auth0, Firebase Auth, and Azure AD B2C.

When to Use Cognito

Good fit:

  • You're all-in on AWS and want auth that integrates natively
  • You need basic auth (sign up, sign in, MFA, password reset)
  • Your budget is limited (Cognito is cheap at scale)
  • You want API Gateway JWT authorizer integration (zero-code auth at the API layer)

Consider alternatives (Auth0, Clerk, etc.) when:

  • You need a polished, customizable login UI out of the box
  • You need advanced features (breached password detection, bot detection, adaptive MFA)
  • Developer experience is a priority (Cognito's docs and SDK are rough)
  • You need strong multi-tenancy with per-tenant branding

The honest take: Cognito works and it's cheap. The developer experience used to be frustrating. The old hosted UI was ugly and the error messages are cryptic. The new Managed Login (launched late 2024) significantly improves this with a no-code visual branding editor for the full user journey (sign-up, login, password recovery, MFA). It's not Auth0-level polish, but it's no longer embarrassing.

Feature Plans

Cognito now has three pricing tiers:

Lite Essentials Plus
Price $0.0055/MAU $0.015/MAU $0.02/MAU
Free tier 10,000 MAU 10,000 MAU None
Managed Login (branded UI) No Yes Yes
Passwordless (passkeys, email OTP) No Yes Yes
Advanced threat protection No No Yes
Compromised credential detection No No Yes
Multi-region replication No Yes Yes
Best for Basic auth, budget-constrained Most production apps High-security, regulated

Essentials is the default for new user pools and covers what most applications need. Lite is roughly the old pricing model. Plus adds the security features that were previously the expensive "Advanced Security Features" add-on. Now bundled and cheaper.

Multi-Region Replication

Launched June 2026. Cognito can now synchronize user data, credentials, and pool configurations to a secondary region in near real-time:

  • One-directional replication (primary β†’ secondary)
  • Users can authenticate against the secondary region during a failover
  • No forced password resets when failing over
  • Available on Essentials and Plus plans (not Lite)

This solves Cognito's biggest enterprise complaint: it was single-region, meaning a regional outage took out authentication entirely. With MRR, you designate a standby region and can fail over without users noticing.

Limitation during failover: New user registrations are disabled in the secondary region. The standby is for authentication continuity, not full read-write operation.

Core Concepts

User Pool

The user directory. Stores usernames, passwords, attributes (email, phone, custom attributes like tenant_id). Issues JWT tokens.

App Client

Your application's registration with the user pool. Defines which auth flows are allowed, token validity periods, and OAuth scopes.

Identity Pool (Cognito Identity)

Separate from User Pools. Exchanges tokens (from User Pools, social providers, or SAML) for temporary AWS credentials. Use when your client needs to call AWS services directly (S3 upload from browser, IoT device registration).

Tokens

Cognito issues three tokens:

  • ID Token: User identity claims (email, name, custom attributes). Use for your application logic.
  • Access Token: Scopes and groups. Use for API authorization.
  • Refresh Token: Long-lived (default 30 days). Exchange for new ID/access tokens without re-authentication.

API Gateway Integration

The cheapest, fastest way to protect an API:

HTTP API + JWT Authorizer

API Gateway validates the token before invoking your backend. No Lambda, no code:

  • Validates JWT signature against Cognito's JWKS
  • Checks token expiration
  • Verifies issuer
  • Passes claims through to the backend in the request context

Invalid tokens get a 401 before your Lambda is even invoked (and you don't pay for the invocation).

REST API + Cognito Authorizer

Similar concept but using the REST API's built-in Cognito integration. Validates tokens and provides user pool claims to the backend.

Custom Attributes

Store application-specific data on the user:

custom:tenant_id β†’ "acme-corp"
custom:role β†’ "admin"
custom:plan β†’ "enterprise"

These appear in the ID token. Useful for multi-tenant applications where you need tenant context on every request without a database lookup.

Limitation: Custom attributes can't be searched or filtered. You can't query "all users in tenant X" from Cognito directly. If you need that, maintain a separate user table in DynamoDB.

Lambda Triggers

Cognito invokes Lambda functions at specific points in the auth flow:

Trigger Use case
Pre Sign-up Validate email domain, block disposable emails
Post Confirmation Create user record in DynamoDB, send welcome email
Pre Token Generation Add custom claims to tokens based on database lookup
Custom Message Customize verification emails
Pre Authentication Check if user is suspended, rate-limit login attempts

Pre Token Generation is the most powerful. Inject dynamic claims (permissions, tenant info) into the token at issuance time.

Managed Login vs Custom UI

Managed Login (recommended starting point)

The new default. AWS hosts the login pages with a visual branding editor. Customize colors, logo, fonts, and layout without code. Covers sign-up, sign-in, password recovery, MFA, and social login screens. Available on Essentials and Plus plans.

Good for:

  • Most applications (it's genuinely decent-looking now)
  • Getting to production fast without building auth UI
  • Teams without frontend resources dedicated to auth screens

Custom UI

Build your own login page entirely. Use Cognito's APIs (InitiateAuth, RespondToAuthChallenge) or the Amplify Auth library. Full control, more work. Use when you need a deeply branded experience or flows that Managed Login doesn't support.

Pricing

Cognito's pricing depends on your feature plan:

  • Lite: First 10,000 MAU free, then $0.0055/user (volume discounts at scale)
  • Essentials: First 10,000 MAU free, then $0.015/user. Includes managed login, passwordless, MRR
  • Plus: $0.02/user (no free tier). Includes threat protection, compromised credential detection

At 100K users on Essentials: ~$1,350/month. Auth0 at the same scale: $2,000+/month. The cost advantage remains, especially at scale.

Common Gotchas

  • Access tokens don't have an aud claim. Only ID tokens do. This trips up JWT validation libraries that expect audience.
  • Custom attributes are immutable by default. Once set, only admins can change them unless you mark them as mutable.
  • User pool configuration is hard to change after creation. Some settings (required attributes, username format) are permanent. Plan carefully before creating a production pool.
  • Cognito has strict rate limits on admin APIs (40 TPS default for operations like AdminCreateUser). Bulk imports need special handling.
  • Token revocation is limited. You can revoke refresh tokens, but access/ID tokens are valid until expiry. Keep token lifetime short (1 hour).

CDK Example

import { UserPool, UserPoolClient, AccountRecovery, Mfa } from 'aws-cdk-lib/aws-cognito';

const userPool = new UserPool(this, 'Users', {
  userPoolName: 'my-app-users',
  selfSignUpEnabled: true,
  signInAliases: { email: true },
  autoVerify: { email: true },
  mfa: Mfa.OPTIONAL,
  passwordPolicy: {
    minLength: 12,
    requireLowercase: true,
    requireUppercase: true,
    requireDigits: true,
    requireSymbols: false,
  },
  accountRecovery: AccountRecovery.EMAIL_ONLY,
  customAttributes: {
    tenant_id: new StringAttribute({ mutable: false }),
  },
  removalPolicy: RemovalPolicy.RETAIN,
});

const client = userPool.addClient('WebApp', {
  authFlows: { userSrp: true },
  oAuth: {
    flows: { authorizationCodeGrant: true },
    scopes: [OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE],
    callbackUrls: ['https://myapp.com/callback'],
    logoutUrls: ['https://myapp.com'],
  },
  accessTokenValidity: Duration.hours(1),
  idTokenValidity: Duration.hours(1),
  refreshTokenValidity: Duration.days(30),
});

Further Reading

Looking for hands-on help? View my AWS architecture services β†’

Building authentication?

Drop me a message β€” I typically respond within one business day.