Modern Auth: OpenID Connect (OIDC)
OAuth 2.0 was designed for authorization — letting one app act on your behalf in another app. But it says nothing about who you are. OpenID Connect (OIDC) is a thin identity layer built on top of OAuth 2.0 that adds the concept of "here is who the user is." Cognito User Pools implements OIDC fully, which means it interoperates with any OIDC-compatible system — API Gateway, ALB, third-party identity providers, and custom backends.
When a user logs in successfully, Cognito doesn't just say "OK." It issues three distinct tokens, each with a specific job.
The Trinity of Tokens
1. ID Token
- Purpose: Carries identity claims — facts about the user.
- Contents: sub (unique user ID), email, name, cognito:groups, and any custom attributes you've added.
- Usage: The frontend reads this to display "Welcome, Alice!" or to show/hide UI elements based on group membership. It is a signed JWT, so the frontend can decode it client-side without a network call.
- Do not use it to authorize API calls. It's for identity display, not access control.
2. Access Token
- Purpose: Grants permission to call protected resources.
- Contents: scope (e.g., openid, email, read:orders), exp (expiration), client_id.
- Usage: Sent in every HTTP request to your API in the Authorization: Bearer <token> header. API Gateway's Cognito Authorizer validates this token's signature and expiration before forwarding the request to your Lambda or backend.
- Lifetime: Configurable, default 1 hour.
3. Refresh Token
- Purpose: Long-lived credential used to silently renew the other two tokens.
- Usage: When the Access Token expires, the client sends the Refresh Token to Cognito's /oauth2/token endpoint and receives a fresh ID Token and Access Token — without prompting the user to log in again.
- Lifetime: Configurable from 1 hour to 10 years. Default is 30 days.
- Security implication: If a Refresh Token is stolen, the attacker has persistent access until it's explicitly revoked. Treat it like a password.
JWT Structure and Validation
A JWT has three base64url-encoded parts separated by dots: header.payload.signature. The header identifies the signing algorithm (RS256 for Cognito). The payload contains the claims. The signature is created using Cognito's private key.
To validate a JWT, your backend fetches Cognito's JSON Web Key Set (JWKS) from a well-known URL (https://cognito-idp.<region>.amazonaws.com/<userPoolId>/.well-known/jwks.json) and uses the public key to verify the signature. API Gateway's built-in Cognito Authorizer does this automatically. If you're writing a custom authorizer, use a library — never implement JWT validation from scratch.
Interviewers frequently ask: "What's the difference between the ID Token and the Access Token, and which one do you send to your API?" The correct answer: send the Access Token to your API for authorization. The ID Token is for the client to learn about the user's identity. Sending the ID Token to your API is a common mistake — it works in practice because both are JWTs signed by Cognito, but it violates the OIDC spec and can create subtle security issues if your API trusts claims that were intended only for the client.