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
- Code Verifier Generation: When a user initiates login, the application generates a random string (code verifier).
- Code Challenge Creation: The application derives a code challenge from the verifier using SHA-256 hashing and Base64URL encoding.
- Authorization Request: The application redirects to Cognito with the code challenge.
- User Authentication: The user authenticates with Cognito.
- Authorization Code: Cognito redirects back to the application with an authorization code.
- 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
Cognito App Client Requirements
Your Cognito App Client must be configured as follows:
- No Client Secret: The app client should be configured as a public client without a client secret.
- Allowed OAuth Flows: Authorization code grant
- Allowed OAuth Scopes:
openid,email,profile(and optionallyphone) - 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
- Run the application:
go run main.go - Visit
http://localhost:8080/loginin 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