← Back

Cognito OAuth Infrastructure Deep Dive: From Tribal Knowledge to Documentation

·auth-security

Cognito OAuth Infrastructure Deep Dive: From Tribal Knowledge to Documentation

OAuth setup was tribal knowledge scattered across Slack threads, undocumented configuration files, and individual team members' mental models. This created a 2-week onboarding bottleneck and caused repeated production incidents from misconfigurations. We centralized all OAuth infrastructure knowledge into comprehensive documentation.

The Problem

New engineers couldn't debug OAuth issues without hunting down senior developers who set up the original configuration:

Common questions:

  • "Which Cognito User Pool do we use for staging?"
  • "Why does the redirect URI include /auth/callback instead of /callback?"
  • "How do I test OAuth locally without breaking production?"
  • "What's the difference between client_id and user_pool_id?"

Consequences:

  • 2+ weeks to onboard engineers on authentication
  • Production incidents from incorrect OAuth configuration
  • Failed deployments due to missing environment variables
  • Time wasted in Slack threads explaining the same concepts

Documentation Before vs After

Before:

Scattered Documentation
┌──────────────────────────────────────┐
│ - Setup steps in Slack threads       │
│ - Config in multiple files           │
│ - No single source of truth          │
│ - Onboarding takes days              │
└──────────────────────────────────────┘

After:

Centralized OAuth Docs
┌──────────────────────────────────────┐
│ CLAUDE.md → "Cognito OAuth Setup"   │
│ - Architecture diagram               │
│ - Step-by-step setup guide           │
│ - Troubleshooting section            │
│ - Environment-specific configs       │
│ - Callback URL patterns              │
└──────────────────────────────────────┘

Cognito OAuth Architecture

High-Level Flow

OAuth 2.0 Authorization Code Flow
┌─────────────┐       ┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ Mobile App  │──────>│ API Gateway │──────>│ Cognito     │──────>│ Google/     │
│             │       │             │       │ User Pool   │       │ Apple       │
│             │       │             │       │             │       │             │
│ 1. Login    │       │ 2. Redirect │       │ 3. OAuth    │       │ 4. Consent  │
│    Request  │       │    to       │       │    Redirect │       │    Screen   │
│             │       │    Cognito  │       │    to IdP   │       │             │
└─────────────┘       └─────────────┘       └─────────────┘       └─────────────┘
                                                                            │
                                                                            v
┌─────────────┐       ┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ Mobile App  │<──────│ API Gateway │<──────│ Cognito     │<──────│ Google/     │
│             │       │             │       │ User Pool   │       │ Apple       │
│             │       │             │       │             │       │             │
│ 8. User     │       │ 7. JWT      │       │ 6. Exchange │       │ 5. Auth     │
│    Profile  │       │    Tokens   │       │    Code for │       │    Code     │
│             │       │             │       │    Tokens   │       │             │
└─────────────┘       └─────────────┘       └─────────────┘       └─────────────┘

Detailed Step-by-Step Flow

Step 1: Mobile App Initiates Login

GET /auth/google/login
Host: api.thurayya.app

Step 2: API Gateway Redirects to Cognito

HTTP/1.1 302 Found
Location: https://thurayya.auth.us-east-1.amazoncognito.com/oauth2/authorize?
  response_type=code&
  client_id=abc123&
  redirect_uri=https://api.thurayya.app/auth/google/callback&
  identity_provider=Google&
  scope=openid+email+profile

Step 3: Cognito Redirects to Google

HTTP/1.1 302 Found
Location: https://accounts.google.com/o/oauth2/v2/auth?
  client_id=google-client-id&
  redirect_uri=https://thurayya.auth.us-east-1.amazoncognito.com/oauth2/idpresponse&
  response_type=code&
  scope=openid+email+profile

Step 4: User Grants Consent on Google User sees Google consent screen, clicks "Allow"

Step 5: Google Returns Authorization Code to Cognito

HTTP/1.1 302 Found
Location: https://thurayya.auth.us-east-1.amazoncognito.com/oauth2/idpresponse?
  code=google-auth-code

Step 6: Cognito Exchanges Code for Tokens Cognito calls Google's token endpoint:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=google-auth-code&
client_id=google-client-id&
client_secret=google-client-secret&
redirect_uri=https://thurayya.auth.us-east-1.amazoncognito.com/oauth2/idpresponse

Cognito receives Google tokens, creates/updates Cognito user, generates Cognito tokens, redirects to our callback:

HTTP/1.1 302 Found
Location: https://api.thurayya.app/auth/google/callback?
  code=cognito-auth-code

Step 7: Backend Exchanges Cognito Code for JWT

# Flask backend
@app.route('/auth/google/callback')
def google_callback():
    code = request.args.get('code')

    # Exchange code for tokens
    response = requests.post(
        'https://thurayya.auth.us-east-1.amazoncognito.com/oauth2/token',
        data={
            'grant_type': 'authorization_code',
            'code': code,
            'client_id': COGNITO_CLIENT_ID,
            'redirect_uri': 'https://api.thurayya.app/auth/google/callback'
        },
        headers={'Content-Type': 'application/x-www-form-urlencoded'}
    )

    tokens = response.json()
    # tokens = {
    #   'id_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...',
    #   'access_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...',
    #   'refresh_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...',
    #   'expires_in': 3600
    # }

    return tokens

Step 8: Mobile App Receives JWT Tokens

{
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600
}

Mobile app stores tokens and uses id_token for API authentication.

Environment-Specific Configuration

Development (Localhost)

# Environment variables
COGNITO_REGION = "us-east-1"
COGNITO_USER_POOL_ID = "us-east-1_devpool"
COGNITO_CLIENT_ID = "abc123dev"
COGNITO_DOMAIN = "thurayya-dev.auth.us-east-1.amazoncognito.com"
REDIRECT_URI = "http://localhost:5000/auth/google/callback"

# Google OAuth (development)
GOOGLE_CLIENT_ID = "google-dev-client-id"
GOOGLE_CLIENT_SECRET = "google-dev-secret"

Staging

COGNITO_REGION = "us-east-1"
COGNITO_USER_POOL_ID = "us-east-1_stagingpool"
COGNITO_CLIENT_ID = "xyz789staging"
COGNITO_DOMAIN = "thurayya-staging.auth.us-east-1.amazoncognito.com"
REDIRECT_URI = "https://staging-api.thurayya.app/auth/google/callback"

GOOGLE_CLIENT_ID = "google-staging-client-id"
GOOGLE_CLIENT_SECRET = "google-staging-secret"

Production

COGNITO_REGION = "us-east-1"
COGNITO_USER_POOL_ID = "us-east-1_prodpool"
COGNITO_CLIENT_ID = "pqr456prod"
COGNITO_DOMAIN = "thurayya.auth.us-east-1.amazoncognito.com"
REDIRECT_URI = "https://api.thurayya.app/auth/google/callback"

GOOGLE_CLIENT_ID = "google-prod-client-id"
GOOGLE_CLIENT_SECRET = "google-prod-secret"

Cognito User Pool Configuration

User Pool Settings

Required attributes:

  • email (required, mutable)
  • name (optional, mutable)
  • picture (optional, mutable)

Password policy:

  • Minimum length: 8 characters
  • Require uppercase: Yes
  • Require lowercase: Yes
  • Require numbers: Yes
  • Require special characters: No (reduces user friction)

MFA: Optional (recommended for production)

App Client Settings

OAuth 2.0 grant types:

  • Authorization code grant (enabled)
  • Implicit grant (disabled - security risk)

OAuth scopes:

  • openid (required)
  • email (required)
  • profile (optional)

Callback URLs:

http://localhost:5000/auth/google/callback
http://localhost:5000/auth/apple/callback
https://staging-api.thurayya.app/auth/google/callback
https://staging-api.thurayya.app/auth/apple/callback
https://api.thurayya.app/auth/google/callback
https://api.thurayya.app/auth/apple/callback

Sign-out URLs:

http://localhost:5000/auth/logout
https://staging-api.thurayya.app/auth/logout
https://api.thurayya.app/auth/logout

Identity Providers

Google:

Provider name: Google
Client ID: [from Google Cloud Console]
Client secret: [from Google Cloud Console]
Authorize scopes: openid email profile
Attribute mapping:
  - email  email
  - name  name
  - picture  picture
  - sub  username (unique identifier)

Apple:

Provider name: Apple
Client ID: [from Apple Developer Portal]
Team ID: [from Apple Developer Portal]
Key ID: [from Apple Developer Portal]
Private key: [.p8 file from Apple]
Authorize scopes: openid email name
Attribute mapping:
  - email → email
  - name → name
  - sub → username

Common Issues and Troubleshooting

Issue 1: redirect_uri_mismatch

Error:

Error: redirect_uri_mismatch
The redirect URI in the request does not match the registered callback URLs

Cause: Redirect URI not registered in Cognito or Google/Apple

Solution:

  1. Check Cognito App Client → Callback URLs
  2. Check Google Cloud Console → OAuth 2.0 Client → Authorized redirect URIs
  3. Ensure exact match (including protocol, domain, path)
  4. Wait 10 minutes after changes (propagation time)

Issue 2: Token Validation Fails

Error:

Error: Invalid token signature
JWT signature verification failed

Cause: Using wrong User Pool ID or Region

Solution:

# Verify token validation configuration
import jwt
from jwt import PyJWKClient

# Correct JWKS URL format
jwks_url = f"https://cognito-idp.{COGNITO_REGION}.amazonaws.com/{COGNITO_USER_POOL_ID}/.well-known/jwks.json"

# Validate token
jwks_client = PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
decoded = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    audience=COGNITO_CLIENT_ID
)

Issue 3: User Attributes Missing

Error:

User profile missing email

Cause: Attribute mapping not configured in Cognito

Solution:

  1. Go to Cognito User Pool → Identity Providers → Google
  2. Check Attribute Mapping
  3. Ensure email → email, name → name, picture → picture
  4. Test OAuth flow again

Issue 4: CORS Errors

Error:

Access to fetch at 'https://thurayya.auth.us-east-1.amazoncognito.com/oauth2/authorize'
from origin 'http://localhost:3000' has been blocked by CORS policy

Cause: Incorrect OAuth flow (using implicit grant instead of authorization code grant)

Solution: Don't make OAuth requests from frontend JavaScript. Use server-side authorization code flow:

  1. Frontend redirects to backend /auth/google/login
  2. Backend redirects to Cognito
  3. Cognito handles OAuth
  4. Cognito redirects to backend /auth/google/callback
  5. Backend returns tokens to frontend

Testing OAuth Locally

Setup Local Environment

# .env.local
COGNITO_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_devpool
COGNITO_CLIENT_ID=abc123dev
COGNITO_DOMAIN=thurayya-dev.auth.us-east-1.amazoncognito.com
REDIRECT_URI=http://localhost:5000/auth/google/callback

GOOGLE_CLIENT_ID=your-dev-client-id
GOOGLE_CLIENT_SECRET=your-dev-secret

Test Flow

  1. Start Flask development server:

    flask run --port=5000
    
  2. Initiate OAuth flow:

    curl -I http://localhost:5000/auth/google/login
    # Should return 302 redirect to Cognito
    
  3. Open redirect URL in browser: Browser redirects through Cognito → Google → Cognito → localhost:5000/callback

  4. Verify token in response:

    # Should receive JWT tokens
    

Security Best Practices

1. Never Commit Secrets

# .gitignore
.env
.env.local
.env.production
*_secret.json
cognito_*.json

2. Rotate Secrets Regularly

  • Google/Apple client secrets: Every 90 days
  • Cognito app client secret: Every 90 days
  • Document rotation in calendar

3. Use Separate User Pools per Environment

  • Development: us-east-1_devpool
  • Staging: us-east-1_stagingpool
  • Production: us-east-1_prodpool

Prevents production user data exposure in testing.

4. Enable MFA for Admin Accounts

All Cognito User Pool administrators must use MFA.

5. Monitor OAuth Errors

# CloudWatch alarm
if oauth_error_rate > 5%:
    alert_engineering_team()
    check_cognito_configuration()

Results

Before documentation:

  • 2-week onboarding time for OAuth knowledge
  • 8 production incidents from OAuth misconfiguration (6 months)
  • 4-5 hours/week answering OAuth questions
  • Zero self-service troubleshooting capability

After documentation:

  • 3-day onboarding time (70% reduction)
  • 0 production incidents from OAuth misconfiguration (2 months)
  • <1 hour/week answering OAuth questions
  • Engineers self-service 90% of OAuth issues

Time investment:

  • Documentation writing: 8 hours
  • Diagram creation: 2 hours
  • Peer review: 2 hours
  • Total: 12 hours

ROI: 12 hours invested saved 4+ hours/week (16+ hours/month)

Key Takeaways

  1. Document OAuth flows end-to-end - From mobile app through all redirects
  2. Include environment-specific configurations - Development, staging, production
  3. Create troubleshooting guides - Common errors with solutions
  4. Diagram the architecture - Visual aids accelerate understanding
  5. Test documentation with new engineers - They'll find gaps immediately

Documentation Checklist

  • [ ] Architecture diagram with all components
  • [ ] Step-by-step OAuth flow with HTTP examples
  • [ ] Environment-specific configuration
  • [ ] Cognito User Pool setup instructions
  • [ ] Identity provider configuration (Google, Apple)
  • [ ] Common errors and solutions
  • [ ] Local testing instructions
  • [ ] Security best practices
  • [ ] Monitoring and alerting setup

Resources


Implementation date: January 2026 Impact: 70% reduction in onboarding time, eliminated OAuth-related production incidents Time investment: 12 hours ROI: 16+ hours/month saved