# 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](https://github.com/labstack/echo) (v4) - Web framework - [JWKS](https://github.com/lestrrat-go/jwx) - 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: ```bash export COGNITO_CLIENT_ID= export COGNITO_USER_POOL_ID= export AWS_REGION= export DEBUG=true # Optional for debug logging ``` 4. Run the application: ```bash go run main.go ``` 5. 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: ```go 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 ```mermaid sequenceDiagram participant Browser participant AppServer as App Server participant Cognito Browser->>AppServer: 1. GET /login Note over AppServer: 2. Generate code_verifier
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
code=...&code_verifier=... Note over Cognito: 13. Validate code and verifier Cognito->>AppServer: 14. Tokens response Note over AppServer: 15. Verify tokens
Check permissions AppServer->>Browser: 16. Authentication successful ```