Skip to content

Latest commit

 

History

History
852 lines (627 loc) · 26.1 KB

File metadata and controls

852 lines (627 loc) · 26.1 KB

AWS Cognito Authentication for Bloom LIMS

Bloom LIMS uses AWS Cognito for single sign-on (SSO) authentication via the hosted UI. In the 2.0 refactor, Bloom depends on daylily-auth-cognito as the shared auth boundary: browser session handling lives in browser.session, Hosted UI URL and code-exchange helpers live in browser.oauth and browser.google, bearer verification lives in runtime.verifier and runtime.m2m, and Cognito lifecycle stays in daycog via admin.*. Session storage remains token-free.


Table of Contents

  1. Prerequisites
  2. AWS Cognito Configuration
  3. Environment Variable Configuration
  4. Local Development Setup
  5. Authentication Flow
  6. Troubleshooting
  7. Security Considerations

Prerequisites

Before setting up Cognito authentication for Bloom LIMS, ensure you have:

AWS Requirements

Requirement Description
AWS Account Active AWS account with billing enabled
IAM Permissions User/role with AmazonCognitoPowerUser policy or equivalent permissions
Region Access Access to your chosen AWS region (e.g., us-east-1, us-west-2)

Required IAM Permissions

Your AWS user/role needs these minimum permissions:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cognito-idp:CreateUserPool",
                "cognito-idp:CreateUserPoolClient",
                "cognito-idp:CreateUserPoolDomain",
                "cognito-idp:UpdateUserPool",
                "cognito-idp:UpdateUserPoolClient",
                "cognito-idp:DescribeUserPool",
                "cognito-idp:DescribeUserPoolClient",
                "cognito-idp:ListUserPools",
                "cognito-idp:ListUserPoolClients"
            ],
            "Resource": "*"
        }
    ]
}

Local Environment Requirements

Requirement Version Purpose
Python 3.10+ Application runtime
PyJWT 2.0+ JWT token validation
python-dotenv Any Environment variable loading

Install required packages:

pip install pyjwt[crypto] python-dotenv

For Google OAuth (Optional)

If using Google as an identity provider:

  1. Google Cloud Project with OAuth 2.0 credentials
  2. OAuth Consent Screen configured
  3. OAuth 2.0 Client ID (Web application type)

AWS Cognito Configuration

Step 1: Create a User Pool

Navigate to Cognito

  1. Log in to the AWS Console
  2. Search for "Cognito" in the services search bar
  3. Click Amazon Cognito
  4. Click Create user pool

Configure Sign-in Experience

  1. Authentication providers: Select Cognito user pool
  2. Cognito user pool sign-in options: Check:
    • ☑️ Email
    • ☐ User name (optional)
    • ☐ Phone number (optional)
  3. Click Next

Configure Security Requirements

  1. Password policy:

    • Mode: Cognito defaults (recommended) or Custom
    • Cognito defaults require: 8+ characters, uppercase, lowercase, number, special character
  2. Multi-factor authentication (MFA):

    • For development: No MFA
    • For production: Optional MFA or Required MFA
  3. User account recovery:

    • ☑️ Enable self-service account recovery
    • Delivery method: Email only
  4. Click Next

Configure Sign-up Experience

  1. Self-registration: Enable or disable based on your needs

    • Enable for open registration
    • Disable if you'll create users manually or via federated identity
  2. Attribute verification:

    • ☑️ Allow Cognito to automatically send messages to verify and confirm
    • Attributes to verify: Email
  3. Required attributes: Select at minimum:

    • ☑️ email
  4. Click Next

Configure Message Delivery

  1. Email provider:

    • For development/testing: Send email with Cognito (50 emails/day limit)
    • For production: Send email with Amazon SES (requires SES setup)
  2. If using Cognito email:

    • FROM email address: no-reply@verificationemail.com (default)
  3. Click Next

Integrate Your App

  1. User pool name: Enter a descriptive name (e.g., bloom-lims-prod or bloom-lims-dev)

  2. Hosted authentication pages:

    • ☑️ Use the Cognito Hosted UI
  3. Domain:

    • Domain type: Select Use a Cognito domain
    • Cognito domain: Enter a unique prefix (e.g., bloom-lims-yourorg)
    • Full domain will be: bloom-lims-yourorg.auth.{region}.amazoncognito.com
  4. Initial app client:

    • App type: Select Public client
    • App client name: bloom-lims-web
    • Client secret: Select Don't generate a client secret

      ⚠️ Important: Bloom uses the authorization-code flow and expects the Hosted UI to redirect back to /auth/callback

  5. Allowed callback URLs:

    https://localhost:8912/auth/callback
    

    Add your production callback URL(s) here as well (e.g., https://bloom.yourcompany.com/auth/callback)

  6. Allowed sign-out URLs:

    https://localhost:8912/
    
  7. Advanced app client settings (expand this section):

    • OAuth 2.0 grant types:

      • Implicit grant
      • ☑️ Authorization code grant
    • OpenID Connect scopes:

      • ☑️ OpenID
      • ☑️ Email
      • ☑️ Profile
  8. Click Next

Review and Create

  1. Review all settings

  2. Click Create user pool

  3. Record these values (you'll need them for Bloom YAML configuration):

    Value Where to Find
    User Pool ID User pool overview → User pool ID (e.g., us-east-1_AbCdEfGhI)
    Client ID App integration → App clients → Client ID
    Domain App integration → Domain → Cognito domain
    Region From User Pool ID prefix (e.g., us-east-1)

Step 2: Configure the App Client

If you need to modify the app client after creation:

  1. Navigate to your User Pool
  2. Go to App integration tab
  3. Scroll to App clients and analytics
  4. Click on your app client name

Hosted UI Settings

Click Edit under Hosted UI:

┌─────────────────────────────────────────────────────────────┐
│ Hosted UI Settings                                          │
├─────────────────────────────────────────────────────────────┤
│ Allowed callback URLs:                                      │
│   https://localhost:8912/auth/callback                      │
│   https://your-production-domain.com/auth/callback          │
│                                                             │
│ Allowed sign-out URLs:                                      │
│   https://localhost:8912/                                   │
│   https://your-production-domain.com/                       │
│                                                             │
│ Identity providers:                                         │
│   ☑️ Cognito user pool                                      │
│   ☑️ Google (if configured)                                 │
│                                                             │
│ OAuth 2.0 grant types:                                      │
│   ☑️ Authorization code grant                               │
│                                                             │
│ OpenID Connect scopes:                                      │
│   ☑️ openid                                                 │
│   ☑️ email                                                  │
│   ☑️ profile                                                │
└─────────────────────────────────────────────────────────────┘

Step 3: Set Up Identity Providers (Optional)

Adding Google as an Identity Provider

Step 3a: Create Google OAuth Credentials
  1. Go to Google Cloud Console
  2. Select or create a project
  3. Navigate to APIs & ServicesCredentials
  4. Click Create CredentialsOAuth client ID
  5. Configure the OAuth consent screen if prompted:
    • User type: External (or Internal for Google Workspace)
    • App name: Bloom LIMS
    • User support email: your email
    • Authorized domains: Add your domain
  6. Create OAuth client ID:
    • Application type: Web application
    • Name: Bloom LIMS Cognito
    • Authorized redirect URIs:
      https://YOUR-COGNITO-DOMAIN.auth.REGION.amazoncognito.com/oauth2/idpresponse
      
      Example:
      https://bloom-lims-yourorg.auth.us-east-1.amazoncognito.com/oauth2/idpresponse
      
  7. Click Create
  8. Save the Client ID and Client Secret
Step 3b: Add Google to Cognito
  1. In AWS Console, navigate to your Cognito User Pool

  2. Go to Sign-in experience tab

  3. Under Federated identity provider sign-in, click Add identity provider

  4. Select Google

  5. Enter:

    • Client ID: From Google Cloud Console
    • Client secret: From Google Cloud Console
    • Authorized scopes: openid email profile
  6. Attribute mapping (map Google attributes to Cognito):

    Google attribute User pool attribute
    email email
    name name
    sub username
  7. Click Add identity provider

Step 3c: Enable Google in App Client
  1. Go to App integration → Your app client
  2. Click Edit under Hosted UI
  3. Under Identity providers, check Google
  4. Save changes

Step 4: Configure the Cognito Domain

Your Cognito domain is set during User Pool creation, but you can modify it:

  1. Navigate to App integration tab
  2. Under Domain, click ActionsCreate Cognito domain or Edit

Using Cognito Domain (Recommended for Development)

  • Format: https://{your-prefix}.auth.{region}.amazoncognito.com
  • Example: https://bloom-lims-dev.auth.us-east-1.amazoncognito.com

Using Custom Domain (Production)

For a custom domain like auth.yourcompany.com:

  1. Create an ACM certificate in us-east-1 region
  2. In Cognito, go to App integrationDomain
  3. Click ActionsCreate custom domain
  4. Enter your custom domain
  5. Select your ACM certificate
  6. Create a CNAME record in your DNS:
    • Name: auth (or your subdomain)
    • Value: The CloudFront distribution domain shown in Cognito

Configuration

BLOOM uses a YAML-based configuration system. Configuration is loaded from:

  1. Environment variables (highest priority) — Use BLOOM_* prefix with __ for nesting
  2. User config file~/.config/bloom-<deployment>/bloom-config-<deployment>.yaml
  3. Template defaultsconfig/bloom-config-template.yaml

Setting Up Configuration

  1. Copy the template to your user config directory:

    mkdir -p ~/.config/bloom-<deployment>
    cp config/bloom-config-template.yaml ~/.config/bloom-<deployment>/bloom-config-<deployment>.yaml
  2. Edit ~/.config/bloom-<deployment>/bloom-config-<deployment>.yaml with your Cognito settings:

    # -----------------------------------------------------------------------------
    # Authentication Configuration
    # -----------------------------------------------------------------------------
    auth:
      # AWS Cognito settings
      cognito_user_pool_id: "us-east-1_AbCdEfGhI"      # Your User Pool ID
      cognito_client_id: "1abc2defgh3ijklmno4pqrst"    # Your App Client ID
      cognito_region: "us-east-1"                       # AWS region
      cognito_domain: "bloom-lims-yourorg.auth.us-east-1.amazoncognito.com"
      cognito_redirect_uri: "https://localhost:8912/auth/callback"
      cognito_logout_redirect_uri: "https://localhost:8912/"
      cognito_scopes:
        - "openid"
        - "email"
        - "profile"
      cognito_allowed_domains: []  # Empty = block all; list each allowed domain explicitly

YAML-Only Runtime Configuration

Bloom runtime now reads Cognito settings from ~/.config/bloom-<deployment>/bloom-config-<deployment>.yaml. Do not rely on COGNITO_*, BLOOM_AUTH__COGNITO_*, or daycog env files for service startup.

Configuration Reference

YAML Key Required Default Description
auth.cognito_region ✅ Yes - AWS region (e.g., us-east-1)
auth.cognito_user_pool_id ✅ Yes - User Pool ID from AWS Console
auth.cognito_client_id ✅ Yes - App Client ID (not secret)
auth.cognito_domain ✅ Yes - Hosted UI domain without https://
auth.cognito_redirect_uri ✅ Yes - Post-login redirect URL
auth.cognito_logout_redirect_uri ⚠️ Recommended Same as redirect Post-logout redirect URL
auth.cognito_scopes ❌ No ["openid", "email", "profile"] OAuth scopes to request
auth.cognito_allowed_domains ❌ No [] (block all) Allowed email domains

Local Development Setup

Quick Start

  1. Set up your configuration (see Configuration above)

  2. Verify your Cognito callback URLs include your local address:

    • https://localhost:8912/auth/callback
    • https://localhost:8912/
  3. Activate the environment and start the application:

    source ./activate <deploy-name>
    bloom server start
  4. Test authentication:

    • Navigate to https://localhost:8912/
    • Click "Login with Cognito"
    • Complete authentication in the Cognito Hosted UI
    • You should be redirected back to Bloom

Development Without Authentication

For rapid local development, you can bypass Cognito:

# Set environment variable
export BLOOM_OAUTH=no

# Or add to ~/.config/bloom/config.yaml under a top-level key (not nested)

⚠️ Warning: Never use BLOOM_OAUTH=no in production. This creates a fake user session without any authentication.

Testing with Multiple Domains

If you need to test from different hosts (e.g., mobile device on same network):

  1. Add the IP/hostname to Cognito callback URLs:

    https://192.168.1.100:8912/auth/callback
    https://192.168.1.100:8912/
    
  2. Update your ~/.config/bloom/config.yaml:

    auth:
      cognito_redirect_uri: "https://192.168.1.100:8912/auth/callback"
      cognito_logout_redirect_uri: "https://192.168.1.100:8912/"

Authentication Flow

Sequence Diagram

sequenceDiagram
    participant U as User Browser
    participant B as Bloom LIMS
    participant C as Cognito Hosted UI
    participant J as Cognito JWKS

    U->>B: GET /login
    B->>U: Render login page with Cognito URL
    U->>C: Click "Login with Cognito"
    C->>C: User authenticates (email/password or Google)
    C->>U: Redirect to auth.cognito_redirect_uri with authorization code
    U->>B: GET /auth/callback?code=...
    B->>C: POST /oauth2/token with code
    C->>B: Return id_token and access_token
    B->>J: Fetch JWKS public keys
    J->>B: Return public keys
    B->>B: Validate JWT signature and claims
    B->>B: Check email domain whitelist
    B->>B: Create session with user data
    B->>U: Redirect to home page
Loading

Code Flow Reference

The authentication is handled by these components:

  1. daylily_auth_cognito.browser.session - Shared browser-session contract:

    • CognitoWebSessionConfig: Dataclass holding the Hosted UI session contract
    • start_cognito_login(): Persists CSRF state and redirects to Cognito
    • complete_cognito_callback(): Exchanges the authorization code asynchronously and stores a normalized session principal
    • SessionPrincipal: Normalized browser session identity
  2. daylily_auth_cognito.browser.oauth - Cognito URL and token-exchange helpers:

    • build_authorization_url(): Builds the Hosted UI login URL
    • exchange_authorization_code_async(): Async code exchange used by the web callback path
  3. daylily_auth_cognito.runtime.verifier - API bearer-token verification:

    • CognitoTokenVerifier: Verification-only JWT boundary for API auth
  4. bloom_lims.gui.routes.auth and bloom_lims.gui.web_session - Bloom-specific browser glue:

    • /login renders the login UI
    • /auth/callback completes the browser login flow
    • /logout clears the local browser session and redirects to Cognito logout
  5. templates/login.html - Login UI:

    • "Login with Cognito" button triggers redirect

Session Data Structure

After successful authentication, request.session["user_data"] contains:

{
    "email": "user@example.com",
    "cognito_username": "google_123456789",  # For federated users
    "cognito_sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "roles": ["READ_WRITE"],
    "service_groups": ["API_ACCESS"],
    # ... additional user preferences and normalized profile data
}

Troubleshooting

Common Issues and Solutions

1. "Missing Cognito configuration" Error

Symptoms: Application fails to start or shows error about missing Cognito YAML settings.

Solutions:

# Check configuration is being loaded
python -c "from bloom_lims.config import get_settings; s = get_settings(); print(s.auth.model_dump())"

Checklist:

  • ~/.config/bloom/config.yaml exists with auth settings
  • No typos in YAML keys
  • YAML syntax is valid (proper indentation, colons, quotes)

2. "Invalid Cognito token" Error

Symptoms: Login appears to work but callback fails with token validation error.

Possible Causes & Solutions:

Cause Solution
Wrong Client ID Verify auth.cognito_client_id matches the App Client in AWS
Wrong Region Verify auth.cognito_region matches the User Pool region
Clock skew Ensure server time is accurate (tokens have expiry)
Wrong User Pool ID Verify auth.cognito_user_pool_id is correct

Debug token issues:

# Decode token without validation to inspect claims
import jwt
token = "your-id-token-here"
decoded = jwt.decode(token, options={"verify_signature": False})
print(f"Issuer: {decoded.get('iss')}")
print(f"Audience: {decoded.get('aud')}")
print(f"Expiry: {decoded.get('exp')}")

3. Redirect URI Mismatch Error

Symptoms: Cognito shows "redirect_mismatch" error after authentication.

Solutions:

  1. Check exact URL match in Cognito Console:

    • Navigate to: User Pool → App integration → App client → Hosted UI
    • Compare callback URLs character-by-character
  2. Common mismatches:

    • Trailing slash: https://localhost:8912/ vs https://localhost:8912
    • Protocol: https:// only
    • Host: localhost vs 127.0.0.1
    • Port: Missing or wrong port number
  3. Add all variations you might use:

    https://localhost:8912/auth/callback
    https://localhost:8912/
    

4. "Email domain not allowed" Error

Symptoms: Login succeeds but Bloom rejects the user.

Solutions:

# Check whitelist configuration in YAML
python -c "from bloom_lims.config import get_settings; s = get_settings(); print(s.auth.cognito_allowed_domains)"

5. Logout Not Working

Symptoms: User clicks logout but remains logged in, or gets an error.

Solutions:

  1. Verify sign-out URL is configured in Cognito:

    • User Pool → App integration → App client → Hosted UI
    • Check "Allowed sign-out URLs"
  2. Check Bloom YAML config:

    • Verify auth.cognito_logout_redirect_uri matches a Cognito Allowed sign-out URL
  3. Clear browser data:

    • Cognito sets its own cookies
    • Clear cookies for *.amazoncognito.com

6. Google Login Not Appearing

Symptoms: Only email/password login shows, no Google button.

Solutions:

  1. Verify Google is enabled for the App Client:

    • User Pool → App integration → App client
    • Edit Hosted UI → Identity providers → Check "Google"
  2. Verify Google IdP is configured:

    • User Pool → Sign-in experience → Federated identity providers
    • Google should be listed and active
  3. Check Google OAuth credentials:

    • Authorized redirect URI must be:
      https://{your-domain}.auth.{region}.amazoncognito.com/oauth2/idpresponse
      

7. CORS Errors in Browser Console

Symptoms: JavaScript errors about CORS when posting to /oauth_callback.

Solutions:

The CORS middleware in main.py should allow all origins for development:

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

For production, restrict origins appropriately.


Debug Mode

Enable detailed logging to troubleshoot issues:

import logging
logging.basicConfig(level=logging.DEBUG)

Or set environment variable:

export LOG_LEVEL=DEBUG
python main.py

Security Considerations

Production Checklist

✅ Configuration Security

  • Never commit ~/.config/bloom/config.yaml to version control
  • Use secrets management (AWS Secrets Manager, HashiCorp Vault) for sensitive values
  • Rotate credentials periodically
  • Set environment: production in your config

✅ HTTPS Only

  • Use https:// for all redirect URIs in production
  • Configure TLS/SSL termination
  • Set secure cookie flags
# In production, ensure cookies are secure
response.set_cookie(
    key="session",
    value=token,
    httponly=True,
    secure=True,  # HTTPS only
    samesite="lax",
    max_age=3600
)

✅ Domain Whitelisting

  • Configure auth.cognito_allowed_domains to restrict access
  • Never use BLOOM_OAUTH=no in production
# Production example in ~/.config/bloom/config.yaml
auth:
  cognito_allowed_domains:
    - "yourcompany.com"
    - "trustedpartner.org"

✅ Cognito Security Settings

In AWS Cognito Console:

  1. Enable MFA for production:

    • User Pool → Sign-in experience → MFA
    • Set to "Required" or "Optional"
  2. Configure password policy:

    • Minimum length: 12+ characters
    • Require: uppercase, lowercase, numbers, symbols
  3. Enable advanced security:

    • User Pool → Sign-in experience → Advanced security
    • Enable adaptive authentication
  4. Set token expiration:

    • User Pool → App integration → App client
    • ID token expiration: 1 hour (default)
    • Access token expiration: 1 hour (default)
    • Refresh token expiration: 30 days (adjust as needed)

✅ Remove Development Configurations

Before deploying to production:

# Remove or comment out
# BLOOM_OAUTH=no

# Remove localhost URLs from Cognito callback/logout URLs
# Keep only production URLs

✅ Monitor and Audit

  1. Enable CloudTrail for Cognito API calls
  2. Set up CloudWatch alarms for:
    • Failed authentication attempts
    • Unusual sign-in activity
  3. Review Cognito logs regularly

Token Security

Bloom validates tokens using these security measures:

  1. JWKS Verification: Tokens are verified against Cognito's public keys
  2. Issuer Validation: Token must be from your User Pool
  3. Audience Validation: Token must be for your App Client
  4. Expiration Check: Expired tokens are rejected
# From auth/cognito/client.py
def validate_token(self, token: str) -> Dict:
    signing_key = self._jwks_client.get_signing_key_from_jwt(token)
    return jwt.decode(
        token,
        signing_key.key,
        algorithms=["RS256"],
        audience=self.config.client_id,  # Validates aud claim
        issuer=self.config.issuer,        # Validates iss claim
    )

Quick Reference

URLs and Endpoints

Endpoint Description
GET /login Displays login page with Cognito button
GET /auth/callback Exchanges Cognito authorization code and completes login
POST /oauth_callback Handles legacy fragment-token fallback after Cognito redirect
GET /logout Clears session and redirects to Cognito logout
GET /user_home Shows user profile with Cognito details

YAML Settings Summary

auth:
  cognito_region: us-east-1
  cognito_user_pool_id: us-east-1_AbCdEfGhI
  cognito_client_id: 1abc2defgh3ijklmno4pqrst
  cognito_domain: your-domain.auth.us-east-1.amazoncognito.com
  cognito_redirect_uri: https://your-app.com/auth/callback
  cognito_logout_redirect_uri: https://your-app.com/
  cognito_allowed_domains:
    - yourcompany.com
  cognito_scopes:
    - openid
    - email
    - profile

Useful AWS CLI Commands

# List User Pools
aws cognito-idp list-user-pools --max-results 10

# Describe User Pool
aws cognito-idp describe-user-pool --user-pool-id us-east-1_AbCdEfGhI

# List App Clients
aws cognito-idp list-user-pool-clients --user-pool-id us-east-1_AbCdEfGhI

# Describe App Client
aws cognito-idp describe-user-pool-client \
    --user-pool-id us-east-1_AbCdEfGhI \
    --client-id your-client-id

Migration from Supabase

If you're migrating from the previous Supabase authentication:

  1. Remove Supabase configuration from ~/.config/bloom/config.yaml:

    # Remove any supabase-related keys if present
  2. Add Cognito configuration as documented in the Configuration section above

  3. Update any custom integrations that referenced Supabase

  4. Test thoroughly before deploying to production

The application automatically detects the auth provider based on configuration.