Docs / Cognito JWT

Cognito: API reference

LocalEmu generates real RS256-signed JWT tokens for Cognito user pools. Tokens are verifiable with standard libraries, JWKS endpoints work, and OIDC discovery is fully functional.

Want a runnable walkthrough? See Cognito: demo & walkthrough.

Token Overview

1. When you call CreateUserPool, LocalEmu generates a unique RSA key pair for that pool
2. Authentication calls (AdminInitiateAuth, InitiateAuth) return RS256-signed JWTs
3. The public key is available at the JWKS endpoint, just like real AWS Cognito
4. Your application's token verification code works unchanged against LocalEmu

Endpoints

Endpoint URL
JWKS <base>/<pool-id>/.well-known/jwks.json
OIDC discovery <base>/<pool-id>/.well-known/openid-configuration
Token issuer (iss claim) <base>/<pool-id>
OAuth2 authorize <base>/oauth2/authorize
OAuth2 token <base>/oauth2/token
OAuth2 userInfo <base>/oauth2/userInfo

<base> is whatever external_service_url() returns for this LocalEmu instance: http://localhost:4566 on a default install, or the value of LOCALEMU_HOST / LOCALEMU_PUBLISH_URL when set. The iss claim in every issued JWT uses the same base, so a JWT verifier configured against the discovery document will accept the tokens this LocalEmu instance signs without an issuer-mismatch error. Only the per-pool URLs (jwks.json, openid-configuration, the iss URL itself) carry the <pool-id> path segment; the OAuth2 endpoints do not.

Token Claims

ID Token

Claim Description
subUnique user identifier (UUID)
emailUser's email address
cognito:usernameThe username used for authentication
cognito:groupsList of groups the user belongs to
token_useAlways id

Access Token

Claim Description
usernameThe authenticated username
client_idThe app client ID used for authentication
scopeOAuth2 scopes granted to the token
token_useAlways access

Refresh Token

Refresh tokens are opaque strings, not JWTs. They can be used with AdminInitiateAuth using the REFRESH_TOKEN_AUTH flow to obtain new ID and access tokens.

Example: Full Authentication Flow

Step 1: Create a user pool

Terminal
$ awsemu cognito-idp create-user-pool \
    --pool-name my-app-pool

UserPool:
  Id: us-east-1_abc123XYZ
  Name: my-app-pool

Step 2: Create an app client

Terminal
$ awsemu cognito-idp create-user-pool-client \
    --user-pool-id us-east-1_abc123XYZ \
    --client-name my-app \
    --explicit-auth-flows ALLOW_ADMIN_USER_PASSWORD_AUTH

UserPoolClient:
  ClientId: 3abcdef12345
  ClientName: my-app

Step 3: Create a user and set their password

Terminal
$ awsemu cognito-idp admin-create-user \
    --user-pool-id us-east-1_abc123XYZ \
    --username testuser \
    --user-attributes Name=email,Value=test@example.com

$ awsemu cognito-idp admin-set-user-password \
    --user-pool-id us-east-1_abc123XYZ \
    --username testuser \
    --password 'Str0ng!Pass' \
    --permanent

Step 4: Authenticate and get tokens

Terminal
$ awsemu cognito-idp admin-initiate-auth \
    --user-pool-id us-east-1_abc123XYZ \
    --client-id 3abcdef12345 \
    --auth-flow ADMIN_USER_PASSWORD_AUTH \
    --auth-parameters USERNAME=testuser,PASSWORD='Str0ng!Pass'

AuthenticationResult:
  AccessToken: eyJraWQiOiJhYmMx...  (truncated)
  IdToken: eyJraWQiOiJhYmMx...      (truncated)
  RefreshToken: a1b2c3d4e5...       (opaque)
  ExpiresIn: 3600
  TokenType: Bearer

Step 5: Fetch the JWKS public key

Terminal
$ curl -s http://localhost:4566/us-east-1_abc123XYZ/.well-known/jwks.json | python3 -m json.tool

{
    "keys": [
        {
            "kty": "RSA",
            "kid": "abc123",
            "use": "sig",
            "alg": "RS256",
            "n": "0vx7agoebGcQ...",
            "e": "AQAB"
        }
    ]
}

Step 6: Check OIDC discovery

Terminal
$ curl -s http://localhost:4566/us-east-1_abc123XYZ/.well-known/openid-configuration | python3 -m json.tool

{
    "issuer": "http://localhost:4566/us-east-1_abc123XYZ",
    "jwks_uri": "http://localhost:4566/us-east-1_abc123XYZ/.well-known/jwks.json",
    "authorization_endpoint": "http://localhost:4566/oauth2/authorize",
    "token_endpoint": "http://localhost:4566/oauth2/token",
    "userinfo_endpoint": "http://localhost:4566/oauth2/userInfo",
    "id_token_signing_alg_values_supported": ["RS256"],
    "response_types_supported": ["code", "token"],
    "scopes_supported": ["openid", "email", "phone", "profile"],
    "subject_types_supported": ["public"],
    "token_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
    ],
    "claims_supported": [
        "sub", "iss", "auth_time",
        "email", "email_verified",
        "phone_number", "phone_number_verified",
        "cognito:username", "cognito:groups"
    ]
}

Python Token Verification

Verify tokens using PyJWT and requests. Install with: pip install PyJWT cryptography requests

verify_token.py
import jwt
import requests

POOL_ID = "us-east-1_abc123XYZ"
JWKS_URL = f"http://localhost:4566/{POOL_ID}/.well-known/jwks.json"

# Fetch the public keys
jwks = requests.get(JWKS_URL).json()
jwk_key = jwks["keys"][0]
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(jwk_key)

# Verify the ID token
id_token = "eyJraWQiOiJhYmMx..."  # from AuthenticationResult

decoded = jwt.decode(
    id_token,
    key=public_key,
    algorithms=["RS256"],
    issuer=f"http://localhost:4566/{POOL_ID}",
    options={"verify_aud": False},
)

print(f"Subject:  {decoded['sub']}")
print(f"Email:    {decoded['email']}")
print(f"Username: {decoded['cognito:username']}")
print(f"Groups:   {decoded.get('cognito:groups', [])}")

API Gateway V2 JWT Authorizers

LocalEmu's Cognito tokens work with API Gateway V2 (HTTP API) JWT authorizers. The authorizer fetches the JWKS from the issuer URL and validates the token signature, expiration, and audience automatically.

1. Set the authorizer's issuer to http://localhost:4566/<pool-id>
2. Set the audience to your app client ID
3. API Gateway fetches JWKS, verifies the RS256 signature, and extracts claims
4. The same authorizer configuration works against both LocalEmu and real AWS