docs
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# Cognito harness for auth tests
|
||||
The code in main.go is a harness for running the auth tests.
|
||||
|
||||
To test it set the following environment variables:
|
||||
```json
|
||||
export COGNITO_CLIENT_ID="552cqkf3640t39ncehkmgpce31";
|
||||
export COGNITO_CLIENT_SECRET="omitted";
|
||||
export COGNITO_DOMAIN="us-east-21y6po8rr8.auth.us-east-2.amazoncognito.com";
|
||||
export COGNITO_USER_POOL_ID="us-east-2_1y6po8rR8";export AWS_REGION="us-east-2"
|
||||
```
|
||||
|
||||
Run the code with `go run main.go` with these ^ environment variables set.
|
||||
Try to login to the user group login test page (this will redirect to localhost:8080/query)
|
||||
https://us-east-21y6po8rr8.auth.us-east-2.amazoncognito.com/login?client_id=552cqkf3640t39ncehkmgpce31&response_type=code&scope=email+openid+phone&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fquery
|
||||
|
||||
If it works you should see a response like this:
|
||||
```json
|
||||
{"authenticated":true,"email":"betot75403@isorax.com","groups":["uploaders","querybuilders"],"username":"testuser"}
|
||||
```
|
||||
|
||||
|
||||
Now that this code is verified it will be extracted and moved into an echo middleware that will handle the auth for the API.
|
||||
|
||||
# AWS Cognito Authentication Flow with Localhost - explainer for the code
|
||||
|
||||
This rest of this document explains the step-by-step flow that occurs when a user authenticates through AWS Cognito and gets redirected to your localhost server
|
||||
in this test harness.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You have an AWS Cognito User Pool set up
|
||||
- You have a test user created in this pool
|
||||
- Your Cognito app client is configured with `http://localhost:8080/query` as an allowed callback URL
|
||||
- The provided Go server is running on your local machine
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
### 1. Initial Authentication Request
|
||||
|
||||
When a user wants to authenticate, they're first directed to the Cognito hosted UI. This happens outside of the code you provided, typically through a link like:
|
||||
|
||||
```
|
||||
https://{domain}.auth.{region}.amazoncognito.com/login?client_id={clientId}&response_type=code&scope=email+openid+profile&redirect_uri=http://localhost:8080/query
|
||||
```
|
||||
|
||||
Where:
|
||||
- `{domain}` is your Cognito domain (from the `COGNITO_DOMAIN` environment variable)
|
||||
- `{region}` is your AWS region (from the `AWS_REGION` environment variable)
|
||||
- `{clientId}` is your app client ID (from the `COGNITO_CLIENT_ID` environment variable)
|
||||
|
||||
### 2. User Login
|
||||
|
||||
The user logs in with their username and password on the Cognito hosted UI.
|
||||
|
||||
### 3. Redirect to Localhost
|
||||
|
||||
After successful authentication, Cognito redirects the user to the specified callback URL:
|
||||
```
|
||||
http://localhost:8080/query?code={authorization_code}
|
||||
```
|
||||
|
||||
The authorization code is a temporary token that your application can exchange for actual access tokens.
|
||||
|
||||
### 4. Server Handles the Callback
|
||||
|
||||
When the user's browser loads the redirect URL, your Go server processes the request through the `HandleCallback` function:
|
||||
|
||||
1. **Extract Authorization Code**:
|
||||
```go
|
||||
code := r.URL.Query().Get("code")
|
||||
```
|
||||
|
||||
2. **Check for Errors**:
|
||||
The function checks if Cognito returned any error messages.
|
||||
|
||||
3. **Get Configuration**:
|
||||
It retrieves your Cognito User Pool ID and AWS region from environment variables or falls back to hardcoded values.
|
||||
|
||||
4. **Fetch JWKS (JSON Web Key Set)**:
|
||||
```go
|
||||
jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", region, userPoolID)
|
||||
```
|
||||
|
||||
The function fetches the JWKS, which contains the public keys needed to verify the JWT tokens that Cognito issues.
|
||||
|
||||
### 5. Exchange Authorization Code for Tokens
|
||||
|
||||
The `VerifyCognitoCode` function handles the token exchange:
|
||||
|
||||
1. **Prepare Request to Token Endpoint**:
|
||||
It creates a request to the Cognito token endpoint with:
|
||||
- `grant_type=authorization_code`
|
||||
- Your client ID
|
||||
- The authorization code
|
||||
- Your redirect URI
|
||||
|
||||
2. **Add Authentication**:
|
||||
If you have a client secret, it adds Basic Authentication.
|
||||
|
||||
3. **Send the Request**:
|
||||
It sends the request to Cognito's token endpoint:
|
||||
```
|
||||
https://{domain}.auth.{region}.amazoncognito.com/oauth2/token
|
||||
```
|
||||
|
||||
4. **Parse the Response**:
|
||||
Cognito returns a response containing:
|
||||
- ID token (contains user information)
|
||||
- Access token (for API access)
|
||||
- Refresh token (for getting new tokens)
|
||||
- Token expiration time
|
||||
|
||||
### 6. Verify the ID Token
|
||||
|
||||
The `verifyToken` function verifies the authenticity of the ID token:
|
||||
|
||||
1. **Determine the Issuer**:
|
||||
```go
|
||||
issuer := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", region, config.UserPoolID)
|
||||
```
|
||||
|
||||
2. **Verify Token Signature**:
|
||||
It uses the JWKS (fetched earlier) to verify the token's cryptographic signature.
|
||||
|
||||
3. **Extract Claims**:
|
||||
After verification, it extracts all the claims (user information) from the token.
|
||||
|
||||
### 7. Extract User Groups
|
||||
|
||||
The `GetUserGroups` function attempts to extract the user's group memberships from the token claims:
|
||||
|
||||
1. **Check Multiple Possible Fields**:
|
||||
```go
|
||||
possibleGroupFields := []string{"cognito:groups", "groups", "custom:groups"}
|
||||
```
|
||||
|
||||
It looks for group information in various possible claim names.
|
||||
|
||||
2. **Handle Different Data Types**:
|
||||
It handles cases where groups might be represented as arrays, strings, or other formats.
|
||||
|
||||
### 8. Return Response to User
|
||||
|
||||
Finally, the server returns a JSON response to the user's browser:
|
||||
|
||||
```json
|
||||
{
|
||||
"authenticated": true,
|
||||
"username": "user123",
|
||||
"email": "user@example.com",
|
||||
"groups": ["admin", "users"]
|
||||
}
|
||||
```
|
||||
|
||||
This confirms successful authentication and provides basic user information.
|
||||
|
||||
## Debugging Information
|
||||
|
||||
Throughout this process, the server outputs various debugging information:
|
||||
|
||||
- Configuration details (client ID, domain, token URL, etc.)
|
||||
- Token request details
|
||||
- Authentication method (Basic Auth or public client)
|
||||
- JWKS URL
|
||||
- Issuer URL
|
||||
|
||||
This information helps in troubleshooting any authentication issues.
|
||||
|
||||
## Security Features
|
||||
|
||||
The implementation includes several security features:
|
||||
|
||||
1. **Token Verification**:
|
||||
It cryptographically verifies the tokens using the JWKS.
|
||||
|
||||
2. **URL Validation**:
|
||||
It validates the JWKS URL to prevent potential security issues.
|
||||
|
||||
3. **Timeouts**:
|
||||
HTTP requests use timeouts to prevent hanging connections.
|
||||
|
||||
4. **Server Timeouts**:
|
||||
The HTTP server uses timeouts to protect against slow client attacks.
|
||||
|
||||
5. **Masking Sensitive Values**:
|
||||
The `maskString` function masks sensitive values in logs.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The server handles various error scenarios:
|
||||
|
||||
- Missing authorization code
|
||||
- Authorization errors from Cognito
|
||||
- Failed token requests
|
||||
- Invalid or expired tokens
|
||||
- Missing user groups
|
||||
|
||||
Each error is properly logged and an appropriate HTTP status code is returned.
|
||||
Reference in New Issue
Block a user