Files
query-orchestration/cmd/cognito_test/cognito.pkce/readme.md
T
2025-03-28 18:00:48 -07:00

11 KiB

AWS Cognito PKCE Authentication Example

This directory contains a test harness for validating AWS Cognito PKCE (Proof Key for Code Exchange) authentication and authorization with Go and the Echo web framework. It demonstrates a complete OAuth 2.0 flow with role-based access control (RBAC). It is temporary and will go away once the final middleware is in place.

What is PKCE?

PKCE (Proof Key for Code Exchange) is an extension to the OAuth 2.0 authorization code flow designed to prevent authorization code interception attacks. It's particularly important for:

  • Public clients (like SPAs and mobile apps)
  • Applications where securely storing a client secret is difficult
  • Applications that run in potentially untrusted environments

PKCE works by using a dynamically created cryptographic challenge derived from a secret code verifier. This prevents attackers from exchanging an intercepted authorization code for tokens without possessing the original code verifier.

How PKCE Works in This Application

  1. Code Verifier Generation: When a user initiates login, the application generates a random string (code verifier).
  2. Code Challenge Creation: The application derives a code challenge from the verifier using SHA-256 hashing and Base64URL encoding.
  3. Authorization Request: The application redirects to Cognito with the code challenge.
  4. User Authentication: The user authenticates with Cognito.
  5. Authorization Code: Cognito redirects back to the application with an authorization code.
  6. Token Exchange: The application exchanges the code and original code verifier for access and ID tokens.

Prerequisites

  • Go 1.16+
  • AWS account with Cognito User Pool
  • Properly configured Cognito App Client (see setup section)

Dependencies

  • Echo (v4) - Web framework
  • JWKS - JSON Web Key Set handling for token verification

Cognito App Client Requirements

Your Cognito App Client must be configured as follows:

  1. No Client Secret: The app client should be configured as a public client without a client secret.
  2. Allowed OAuth Flows: Authorization code grant
  3. Allowed OAuth Scopes: openid, email, profile (and optionally phone)
  4. Callback URL: Must include http://localhost:8080/query (or your custom endpoint)

Environment Variables

Variable Description Example Required
COGNITO_CLIENT_ID Your Cognito App Client ID 6u3ppajoksbkdlteacg0ukbnfi Yes
COGNITO_USER_POOL_ID Your Cognito User Pool ID us-east-2_1y6po8rR8 Yes
AWS_REGION AWS Region of your Cognito User Pool us-east-2 Yes
COGNITO_DOMAIN Your Cognito domain your-domain.auth.us-east-2.amazoncognito.com No (falls back to default)
DEBUG Enable debug logging true No (defaults to false)

Creating a Compatible Cognito App Client

You can create a compatible app client with the AWS CLI:

Creating a cognito app client for cognito pkce /login

Assumed the cognito user pool is already created.

aws --profile aarete --region us-east-2 --endpoint-url https://cognito-idp.us-east-2.amazonaws.com cognito-idp create-user-pool-client \
  --user-pool-id us-east-2_1y6po8rR8 \
  --client-name "public-pkce-client" \
  --no-generate-secret \
  --refresh-token-validity 5 \
  --access-token-validity 60 \
  --id-token-validity 60 \
  --token-validity-units "RefreshToken=days,IdToken=minutes,AccessToken=minutes" \
  --explicit-auth-flows ALLOW_REFRESH_TOKEN_AUTH ALLOW_USER_AUTH ALLOW_USER_SRP_AUTH \
  --supported-identity-providers COGNITO \
  --callback-urls http://localhost:8080/query \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes email openid phone profile \
  --allowed-o-auth-flows-user-pool-client \
  --prevent-user-existence-errors ENABLED

Setup and Running

1.2 omitted 3. Set your environment variables:

export COGNITO_CLIENT_ID=<your-client-id>
export COGNITO_USER_POOL_ID=<your-user-pool-id>
export AWS_REGION=<your-region>
export DEBUG=true  # Optional for debug logging
  1. Run the application:
    go run main.go
    
  2. Visit http://localhost:8080/login in your browser to start the authentication flow

Key Features

  • PKCE Implementation: Secure authentication flow without client secrets
  • Token Validation: JWT verification using Cognito's JWKS
  • Authorization: Role-based access control using Cognito groups
  • Caching: JWKS caching to minimize API calls
  • Token Handling: Access and ID token management
  • Middleware Architecture: Authentication and authorization via Echo middleware

Authorization & Groups

This application implements role-based access control using Cognito user groups. Routes can be protected based on group membership:

routePermissions := map[string][]string{
    "/users":     {"exporters"},
    "/users/:id": {"exporters", "querybuilders"},
    // Other routes and permissions
}

Users must belong to the required groups to access protected routes.

Security Considerations

  • The application uses in-memory state management for PKCE sessions. In production, use a persistent and scalable storage solution.
  • The application has a 5-minute expiration for PKCE sessions.
  • Proper error handling prevents information leakage.
  • Token verification checks issuer, signature, and expiration.

Flow Diagram

┌─────────┐                   ┌───────────┐                   ┌─────────┐
│ Browser │                   │ App Server│                   │ Cognito │
└────┬────┘                   └─────┬─────┘                   └────┬────┘
│                              │                              │
│ 1. GET /login                │                              │
│ ─────────────────────────────►                              │
│                              │                              │
│                              │ 2. Generate code_verifier    │
│                              │    Generate code_challenge   │
│                              │                              │
│ 3. Redirect to Cognito       │                              │
│ ◄─────────────────────────────                              │
│                              │                              │
│ 4. GET /oauth2/authorize?...code_challenge                  │
│ ────────────────────────────────────────────────────────────►
│                              │                              │
│                              │                              │ 5. Process auth
│                              │                              │    request
│ 6. Cognito Login Page        │                              │
│ ◄────────────────────────────────────────────────────────────
│                              │                              │
│ 7. User credentials          │                              │
│ ────────────────────────────────────────────────────────────►
│                              │                              │
│                              │                              │ 8. Validate
│                              │                              │    credentials
│ 9. Redirect with code        │                              │
│ ◄────────────────────────────────────────────────────────────
│                              │                              │
│ 10. GET /query?code=...      │                              │
│ ─────────────────────────────►                              │
│                              │                              │
│                              │ 11. Retrieve code_verifier   │
│                              │                              │
│                              │ 12. POST /oauth2/token       │
│                              │     code=...&code_verifier=...
│                              │ ─────────────────────────────►
│                              │                              │
│                              │                              │ 13. Validate
│                              │                              │     code and
│                              │                              │     verifier
│                              │ 14. Tokens response          │
│                              │ ◄─────────────────────────────
│                              │                              │
│                              │ 15. Verify tokens            │
│                              │     Check permissions        │
│                              │                              │
│ 16. Authentication successful│                              │
│ ◄─────────────────────────────                              │
│                              │                              │

Mermaid Sequence Diagram

sequenceDiagram
    participant Browser
    participant AppServer as App Server
    participant Cognito
    
    Browser->>AppServer: 1. GET /login
    Note over AppServer: 2. Generate code_verifier<br/>Generate code_challenge
    AppServer->>Browser: 3. Redirect to Cognito
    Browser->>Cognito: 4. GET /oauth2/authorize?...code_challenge
    Note over Cognito: 5. Process auth request
    Cognito->>Browser: 6. Cognito Login Page
    Browser->>Cognito: 7. User credentials
    Note over Cognito: 8. Validate credentials
    Cognito->>Browser: 9. Redirect with code
    Browser->>AppServer: 10. GET /query?code=...
    Note over AppServer: 11. Retrieve code_verifier
    AppServer->>Cognito: 12. POST /oauth2/token<br/>code=...&code_verifier=...
    Note over Cognito: 13. Validate code and verifier
    Cognito->>AppServer: 14. Tokens response
    Note over AppServer: 15. Verify tokens<br/>Check permissions
    AppServer->>Browser: 16. Authentication successful