diff --git a/internal/cognitoauth/cognitotest/auto_refresh_test.go b/internal/cognitoauth/cognitotest/auto_refresh_test.go index 6e7633db..70f47aab 100644 --- a/internal/cognitoauth/cognitotest/auto_refresh_test.go +++ b/internal/cognitoauth/cognitotest/auto_refresh_test.go @@ -1,3 +1,25 @@ +// Package cognitotest provides end-to-end integration tests for the Cognito authentication system. +// +// This package contains comprehensive tests that validate the complete authentication flow, +// including the critical auto-refresh token functionality. The tests use real browser automation +// via chromedp to simulate actual user interactions and test the full authentication lifecycle. +// +// Key Features Tested: +// - Full OAuth/PKCE authentication flow with AWS Cognito +// - Automatic token refresh when tokens expire +// - Cookie-based token storage and management +// - Integration with Permit.io for authorization +// - Real browser interaction using Chrome/Chromium +// - Natural token expiration testing (5-minute tokens) +// +// Test Requirements: +// - Valid AWS Cognito configuration +// - Test user credentials +// - Chrome/Chromium browser available +// - ENABLE_REFRESH_TESTS=true environment variable +// +// The tests are designed to catch regressions in the authentication system and ensure +// that users can maintain authenticated sessions without manual re-authentication. package cognitotest import ( @@ -24,7 +46,34 @@ import ( "github.com/chromedp/chromedp" ) -// setupEnvironmentForRefresh ensures all required environment variables are set for refresh testing +// setupEnvironmentForRefresh ensures all required environment variables are set for refresh testing. +// +// This function validates that all necessary Cognito configuration and test credentials +// are available in the environment. It's a prerequisite step that must pass before any +// authentication testing can begin. +// +// Required Environment Variables: +// - USER: Test user email/username for Cognito authentication +// - PASSWORD: Test user password for Cognito authentication +// - COGNITO_DOMAIN: AWS Cognito hosted UI domain URL +// - AWS_REGION: AWS region where Cognito user pool is located +// - COGNITO_CLIENT_SECRET: OAuth client secret for Cognito app client +// - COGNITO_USER_POOL_ID: Cognito user pool identifier +// - COGNITO_CLIENT_ID: Cognito app client identifier +// - HEADLESS: Optional, controls if browser runs in headless mode (defaults to "true") +// - DEBUG: Set to "false" for cleaner test output +// +// The function will fail the test immediately if any required variable is missing, +// ensuring tests don't run with incomplete configuration. +// +// Parameters: +// - t: Testing context for logging and test control +// +// Behavior: +// - Validates all required environment variables are present +// - Sets HEADLESS to "true" if not specified +// - Calls t.Fatalf() if any required variable is missing +// - Re-exports all variables to ensure they're available to subprocesses func setupEnvironmentForRefresh(t *testing.T) { // Define all required environment variables with test values envVars := map[string]string{ @@ -54,7 +103,42 @@ func setupEnvironmentForRefresh(t *testing.T) { } } -// setupTestServerForRefresh creates and configures an Echo server for refresh testing +// setupTestServerForRefresh creates and configures an Echo server for refresh testing. +// +// This function sets up a complete web server with authentication middleware that +// mimics the production environment. It's essential for testing the auto-refresh +// functionality because it provides the protected endpoints that trigger token +// refresh when accessed with expired tokens. +// +// Server Configuration: +// - Listens on localhost:8080 (matches Cognito redirect URI configuration) +// - Uses Echo web framework with logging and recovery middleware +// - Registers complete Cognito auth middleware stack +// - Configures Permit.io authorization for protected routes +// - Sets up structured logging at debug level for detailed test output +// +// Test Endpoints Created: +// - /home: Public endpoint that shows authentication status and user info +// - /query: Protected endpoint requiring "exporters", "uploaders", or "querybuilders" role +// - All Cognito auth routes: /login, /login-callback, /logout +// +// The /query endpoint is crucial for auto-refresh testing because: +// - It requires valid authentication (triggers token validation) +// - It's protected by Permit.io authorization (requires valid user context) +// - It returns a success response when auth succeeds +// - It's the endpoint we hit to trigger auto-refresh when tokens expire +// +// Parameters: +// - t: Testing context for logging and test control +// +// Returns: +// - *http.Server: Configured HTTP server ready to start +// - *auth.CognitoConfig: Auth configuration used by the server +// +// Usage: +// - Server must be started in a goroutine: go server.ListenAndServe() +// - Server should be gracefully shutdown after test: server.Shutdown(ctx) +// - Config contains auth paths needed for browser navigation func setupTestServerForRefresh(t *testing.T) (*http.Server, *auth.CognitoConfig) { // Create a new Echo instance e := echo.New() @@ -120,7 +204,46 @@ func setupTestServerForRefresh(t *testing.T) (*http.Server, *auth.CognitoConfig) return server, config } -// parseJWTTokenExpiration extracts the expiration time from a JWT token without verification +// parseJWTTokenExpiration extracts the expiration time from a JWT token without verification. +// +// This function performs client-side JWT parsing to extract the token's expiration time +// without requiring signature verification or external dependencies. It's specifically +// designed for testing scenarios where we need to know when a token expires so we can +// wait for natural expiration and test auto-refresh functionality. +// +// JWT Structure Parsing: +// - Expects standard JWT format: header.payload.signature +// - Extracts and base64-decodes the payload (middle section) +// - Parses JSON payload to access claims +// - Specifically looks for the "exp" (expiration) claim +// +// Key Features: +// - No signature verification (testing purposes only) +// - Handles base64 URL encoding with proper padding +// - Converts Unix timestamp to Go time.Time +// - Robust error handling for malformed tokens +// +// Security Note: +// This function is for testing only and should never be used in production +// code where token signature verification is required for security. +// +// Parameters: +// - tokenString: Raw JWT token string (header.payload.signature format) +// +// Returns: +// - time.Time: Token expiration time in UTC +// - error: Error if token is malformed or missing expiration claim +// +// Error Conditions: +// - Invalid JWT format (not 3 parts separated by dots) +// - Failed base64 decoding of payload +// - Invalid JSON in payload +// - Missing "exp" claim +// - Invalid expiration value type (not a number) +// +// Usage in Testing: +// This enables the test to calculate exactly how long to wait for token +// expiration, allowing for precise timing of auto-refresh scenarios. func parseJWTTokenExpiration(tokenString string) (time.Time, error) { // Split JWT into parts parts := strings.Split(tokenString, ".") @@ -161,7 +284,75 @@ func parseJWTTokenExpiration(tokenString string) (time.Time, error) { } } -// TestAutoRefreshFlow runs the complete end-to-end test for auto refresh functionality +// TestAutoRefreshFlow runs the complete end-to-end test for auto refresh functionality. +// +// This is the main integration test that validates the entire auto-refresh token lifecycle +// using real browser automation and natural token expiration. It's designed to catch +// regressions in the authentication system that could break seamless user sessions. +// +// Test Flow Overview: +// 1. Environment Setup: Validates all required configuration +// 2. Server Setup: Creates test server with auth middleware +// 3. Browser Setup: Configures Chrome with appropriate options +// 4. Initial Authentication: Complete OAuth/PKCE login flow +// 5. Token Extraction: Parses JWT to determine expiration time +// 6. Expiration Wait: Waits for natural token expiration (up to 5+ minutes) +// 7. Auto-Refresh Test: Triggers refresh by accessing protected endpoint +// 8. Verification: Confirms new token and continued access +// +// Key Testing Aspects: +// - Uses real AWS Cognito for authentication (not mocked) +// - Uses real browser (Chrome/Chromium) for authentic user experience +// - Tests natural token expiration (5-minute tokens from Cognito) +// - Validates cookie-based token storage and retrieval +// - Tests non-rotating refresh token handling +// - Verifies Permit.io authorization integration +// - Confirms seamless user experience (no re-authentication required) +// +// Browser Automation Details: +// - Supports both headless and visible browser modes +// - Handles Cognito's hosted UI authentication forms +// - Waits for OAuth redirects and callback handling +// - Captures and analyzes HTTP cookies for token storage +// - Tests protected endpoint access before and after refresh +// +// Time Requirements: +// - Test duration: 5-10 minutes (depends on token expiration time) +// - Extended timeout: 15 minutes to handle longer token lifecycles +// - Progress logging: Shows wait progress every 30 seconds +// +// Test Phases (Subtests): +// 1. "Initial Authentication": Complete login flow and cookie capture +// 2. "Extract Token and Log Expiration": JWT parsing and timing calculation +// 3. "Wait for Token Expiration": Patient wait for natural expiration +// 4. "Test Auto Refresh Functionality": Core auto-refresh validation +// 5. "Verify Continued Access After Refresh": Confirms sustained access +// +// Success Criteria: +// - User successfully authenticates with Cognito +// - JWT tokens are properly stored in HTTP cookies +// - Token expiration time is correctly parsed and tracked +// - Auto-refresh triggers when accessing protected endpoints with expired tokens +// - New tokens are generated and stored without user intervention +// - Protected endpoints remain accessible after token refresh +// - Token expiration times are extended after refresh +// +// Failure Detection: +// - Missing or invalid authentication tokens +// - Failed access to protected endpoints +// - Redirect to login page (indicates failed auto-refresh) +// - Same token before and after refresh attempt +// - Earlier expiration time after refresh attempt +// +// Environment Requirements: +// - ENABLE_REFRESH_TESTS=true (test is skipped otherwise) +// - Valid Cognito configuration (domain, client ID, secret, etc.) +// - Test user credentials (USER, PASSWORD environment variables) +// - Chrome/Chromium browser available on system +// - Network access to AWS Cognito services +// +// This test is crucial for ensuring users don't experience authentication +// interruptions during long sessions, maintaining a smooth user experience. func TestAutoRefreshFlow(t *testing.T) { if os.Getenv("ENABLE_REFRESH_TESTS") != "true" { t.Skip("Skipping auto refresh tests - set ENABLE_REFRESH_TESTS=true to run") @@ -241,6 +432,9 @@ func TestAutoRefreshFlow(t *testing.T) { var cookies []*network.Cookie // Phase 1: Initial Authentication + // This phase performs the complete OAuth/PKCE authentication flow using the browser. + // It navigates to the Cognito hosted UI, submits credentials, handles redirects, + // and captures authentication cookies containing JWT tokens. t.Run("Initial Authentication", func(t *testing.T) { // Navigate to login page loginURL := fmt.Sprintf("%s%s", baseURL, config.GetAuthLoginPath()) @@ -312,6 +506,9 @@ func TestAutoRefreshFlow(t *testing.T) { }) // Phase 2: Extract and Log Token Expiration + // This phase extracts the JWT access token from cookies and parses it to determine + // the exact expiration time. This information is crucial for calculating how long + // to wait before testing the auto-refresh functionality. var authToken string var tokenExpiration time.Time t.Run("Extract Token and Log Expiration", func(t *testing.T) { @@ -335,6 +532,9 @@ func TestAutoRefreshFlow(t *testing.T) { }) // Phase 3: Wait for Token Expiration + // This phase waits for the natural expiration of the JWT access token. + // It calculates the exact wait time and provides progress updates during the wait. + // This is essential for testing real-world token expiration scenarios. t.Run("Wait for Token Expiration", func(t *testing.T) { // Calculate when the token will expire (plus 1 second buffer) timeToWait := time.Until(tokenExpiration) + 1*time.Second @@ -368,6 +568,9 @@ func TestAutoRefreshFlow(t *testing.T) { }) // Phase 4: Test Auto Refresh + // This is the core phase that tests the auto-refresh functionality. + // It attempts to access a protected endpoint with an expired token, expecting + // the middleware to automatically refresh the token and grant access. t.Run("Test Auto Refresh Functionality", func(t *testing.T) { // Get cookies before the request var cookiesBefore []*network.Cookie @@ -482,6 +685,8 @@ func TestAutoRefreshFlow(t *testing.T) { }) // Phase 5: Verify Continued Access to Protected Endpoint + // This final phase confirms that the auto-refreshed token works for subsequent + // requests, ensuring sustained access to protected resources without user intervention. t.Run("Verify Continued Access After Refresh", func(t *testing.T) { // Make another request to a protected endpoint to ensure the refreshed token works queryURL := fmt.Sprintf("%s/query", baseURL) diff --git a/internal/cognitoauth/cognitotest/test_refresh.sh b/internal/cognitoauth/cognitotest/test_refresh.sh new file mode 100755 index 00000000..ed93c6e7 --- /dev/null +++ b/internal/cognitoauth/cognitotest/test_refresh.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# start the queryAPI service and the + + +# Auto Refresh Token Test Script +# This script sets up the environment and runs the auto refresh token test + +export USER=betot75403@isorax.com +export PASSWORD=qqQQ11!! +export COGNITO_DOMAIN=https://us-east-21y6po8rr8.auth.us-east-2.amazoncognito.com +export AWS_REGION=us-east-2 +export COGNITO_CLIENT_SECRET= +export COGNITO_USER_POOL_ID=us-east-2_1y6po8rR8 +export COGNITO_CLIENT_ID=552cqkf3640t39ncehkmgpce31 +export DEBUG=true +export HEADLESS=false +export ENABLE_REFRESH_TESTS=true +export PERMIT_IO_API_KEY=permit_key_ + +echo "🚀 Starting Auto Refresh Token Test" +echo "⚠️ This test will wait for token expiration (up to 5+ minutes)" +echo "🔧 Make sure Cognito tokens are configured with 5 minute expiration" +echo "🌐 Browser will open in non-headless mode for debugging" +echo "" + +# Run the auto refresh test with extended timeout for token expiration +go test -run TestAutoRefreshFlow -timeout 15m -v + +echo "" +echo "✅ Auto refresh test completed" \ No newline at end of file diff --git a/internal/cognitoauth/middleware.go b/internal/cognitoauth/middleware.go index ecfba413..2d203eea 100644 --- a/internal/cognitoauth/middleware.go +++ b/internal/cognitoauth/middleware.go @@ -124,6 +124,203 @@ func performPermitIOAuthorization(c echo.Context, requestPath string, config aut return nil } +// HTTPError represents an HTTP error response with status code and response data +type HTTPError struct { + StatusCode int + Response interface{} +} + +func (e *HTTPError) Error() string { + return fmt.Sprintf("HTTP %d error", e.StatusCode) +} + +// attemptTokenRefresh handles the complete token refresh flow including non-rotating token preservation. +// +// This function consolidates the duplicate refresh logic that was scattered throughout the middleware. +// It attempts to refresh tokens using the provided refresh token and handles the case where Cognito +// returns empty refresh tokens (non-rotating tokens). +// +// Parameters: +// - c: The Echo context for setting cookies and headers +// - refreshToken: The refresh token to use for getting new tokens +// - config: Configuration provider for auth settings +// +// Returns: +// - *TokenResponse: New tokens if refresh succeeds +// - error: Error if refresh fails +func attemptTokenRefresh(c echo.Context, refreshToken string, config auth.ConfigProvider) (*TokenResponse, error) { + config.GetAuthLogger().Debug("Attempting token refresh with refresh token") + + newTokens, err := refreshTokens(refreshToken, config) + if err != nil { + config.GetAuthLogger().Warn("Token refresh failed", "error", err) + clearTokenCookies(c, config) + return nil, err + } + + // Handle non-rotating refresh tokens - preserve original if new one is empty + if newTokens.RefreshToken == "" { + config.GetAuthLogger().Debug("Refresh response missing refresh token, preserving original") + newTokens.RefreshToken = refreshToken + } + + // Update cookies with new tokens + newExpiresAt := time.Now().Add(time.Duration(newTokens.ExpiresIn) * time.Second) + setTokenCookies(c, newTokens, newExpiresAt, config) + + // Update the Authorization header for this request + c.Request().Header.Set("Authorization", "Bearer "+newTokens.AccessToken) + + config.GetAuthLogger().Debug("Token refresh successful, proceeding with new token") + return newTokens, nil +} + +// acquireValidToken gets a valid access token through extraction or refresh. +// +// This function handles the complete token acquisition flow: +// - Attempts to extract token from header/cookie +// - Falls back to refresh if no token found +// - Checks for expiration and refreshes if needed +// - Returns appropriate HTTP error responses +// +// Parameters: +// - c: The Echo context for cookies, headers, and responses +// - config: Configuration provider for auth settings +// +// Returns: +// - string: Valid access token +// - error: HTTP error response if token acquisition fails +func acquireValidToken(c echo.Context, config auth.ConfigProvider) (string, error) { + // Extract JWT token from header or cookie + tokenStr, err := extractToken(c, config) + + // If token extraction fails, try refresh before returning 401 + if err != nil { + config.GetAuthLogger().Debug("No access token found, checking for refresh token") + config.GetAuthLogger().Debug("extractToken error", "error", err) + + // Check if we have a refresh token + config.GetAuthLogger().Debug("About to call getRefreshTokenFromCookie") + refreshToken := getRefreshTokenFromCookie(c, config) + config.GetAuthLogger().Debug("getRefreshTokenFromCookie returned", "token_length", len(refreshToken)) + + if refreshToken == "" { + config.GetAuthLogger().Debug("No refresh token found, redirecting to login") + return "", &HTTPError{ + StatusCode: http.StatusUnauthorized, + Response: map[string]interface{}{ + "error": "Authorization required", + "message": "This endpoint requires authentication. Please obtain a valid JWT token.", + "login_url": config.GetAuthLoginPath(), + }, + } + } + + // Attempt to refresh tokens + newTokens, err := attemptTokenRefresh(c, refreshToken, config) + if err != nil { + return "", &HTTPError{ + StatusCode: http.StatusUnauthorized, + Response: map[string]interface{}{ + "error": "Session expired", + "message": "Unable to refresh your session. Please log in again.", + "login_url": config.GetAuthLoginPath(), + }, + } + } + + // Use the new token for this request + tokenStr = newTokens.AccessToken + } + + // Check if token is expired and attempt refresh if needed + if isTokenExpired(tokenStr) { + config.GetAuthLogger().Debug("Access token expired, attempting refresh") + + refreshToken := getRefreshTokenFromCookie(c, config) + if refreshToken == "" { + config.GetAuthLogger().Debug("No refresh token found, redirecting to login") + clearTokenCookies(c, config) + return "", &HTTPError{ + StatusCode: http.StatusUnauthorized, + Response: map[string]interface{}{ + "error": "Session expired", + "message": "Your session has expired. Please log in again.", + "login_url": config.GetAuthLoginPath(), + }, + } + } + + // Attempt to refresh tokens + newTokens, err := attemptTokenRefresh(c, refreshToken, config) + if err != nil { + return "", &HTTPError{ + StatusCode: http.StatusUnauthorized, + Response: map[string]interface{}{ + "error": "Session expired", + "message": "Unable to refresh your session. Please log in again.", + "login_url": config.GetAuthLoginPath(), + }, + } + } + + // Update the token string for subsequent verification + tokenStr = newTokens.AccessToken + config.GetAuthLogger().Info("Token refreshed successfully") + } + + return tokenStr, nil +} + +// verifyTokenAndSetContext verifies token signature and populates request context. +// +// This function handles token verification and context setup: +// - Retrieves JWKS for token signature verification (unless in test mode) +// - Verifies the token against the key set +// - Extracts claims and user info +// - Populates the Echo context with user information +// +// Parameters: +// - c: The Echo context for setting context values and responses +// - tokenStr: The JWT token string to verify +// - config: Configuration provider for auth settings +// +// Returns: +// - map[string]interface{}: Token claims if verification succeeds +// - error: HTTP error response if verification fails +func verifyTokenAndSetContext(c echo.Context, tokenStr string, config auth.ConfigProvider) (map[string]interface{}, error) { + // Get JWKS (skip in test mode) + var keySet jwk.Set + var err error + if !config.GetNoJWTValidation() { + keySet, err = GetJWKS(config.GetAuthJwksURL(), config.GetAuthLogger()) + if err != nil { + config.GetAuthLogger().Error("Failed to get JWKS", "error", err) + return nil, &HTTPError{ + StatusCode: http.StatusInternalServerError, + Response: map[string]string{"error": "Failed to verify token"}, + } + } + } + + // Verify token (keySet can be nil in test mode) + claims, err := verifyToken(tokenStr, keySet, config) + if err != nil { + config.GetAuthLogger().Warn("Token verification failed", "error", err) + return nil, &HTTPError{ + StatusCode: http.StatusUnauthorized, + Response: map[string]string{"error": "Invalid token"}, + } + } + + // Store user info in context + c.Set("user_claims", claims) + userInfo := ExtractUserInfo(claims) + c.Set("user_info", userInfo) + + return claims, nil +} + // TokenValidationMiddleware verifies JWT tokens and enforces authentication and authorization. // // This middleware performs several critical security functions: @@ -158,127 +355,24 @@ func TokenValidationMiddleware(config auth.ConfigProvider) echo.MiddlewareFunc { return err } - // Extract JWT token from header or cookie - tokenStr, err := extractToken(c, config) - - // If token extraction fails, try refresh before returning 401 + // Acquire valid token (handles extraction, refresh, expiration) + tokenStr, err := acquireValidToken(c, config) if err != nil { - config.GetAuthLogger().Debug("No access token found, checking for refresh token") - config.GetAuthLogger().Debug("extractToken error", "error", err) - - // Check if we have a refresh token - config.GetAuthLogger().Debug("About to call getRefreshTokenFromCookie") - refreshToken := getRefreshTokenFromCookie(c, config) - config.GetAuthLogger().Debug("getRefreshTokenFromCookie returned", "token_length", len(refreshToken)) - - if refreshToken == "" { - config.GetAuthLogger().Debug("No refresh token found, redirecting to login") - return c.JSON(http.StatusUnauthorized, map[string]interface{}{ - "error": "Authorization required", - "message": "This endpoint requires authentication. Please obtain a valid JWT token.", - "login_url": config.GetAuthLoginPath(), - }) + if httpErr, ok := err.(*HTTPError); ok { + return c.JSON(httpErr.StatusCode, httpErr.Response) } - - // Attempt to refresh tokens - config.GetAuthLogger().Debug("Attempting token refresh with refresh token") - newTokens, err := refreshTokens(refreshToken, config) - if err != nil { - config.GetAuthLogger().Warn("Token refresh failed", "error", err) - clearTokenCookies(c, config) - return c.JSON(http.StatusUnauthorized, map[string]interface{}{ - "error": "Session expired", - "message": "Unable to refresh your session. Please log in again.", - "login_url": config.GetAuthLoginPath(), - }) - } - - // Handle non-rotating refresh tokens - preserve original if new one is empty - if newTokens.RefreshToken == "" { - config.GetAuthLogger().Debug("Refresh response missing refresh token, preserving original") - newTokens.RefreshToken = refreshToken - } - - // Update cookies with new tokens - newExpiresAt := time.Now().Add(time.Duration(newTokens.ExpiresIn) * time.Second) - setTokenCookies(c, newTokens, newExpiresAt, config) - - // Use the new token for this request - tokenStr = newTokens.AccessToken - config.GetAuthLogger().Debug("Token refresh successful, proceeding with new token") - - // Update the Authorization header for this request - c.Request().Header.Set("Authorization", "Bearer "+tokenStr) + return err } - // Check if token is expired and attempt refresh if needed - if isTokenExpired(tokenStr) { - config.GetAuthLogger().Debug("Access token expired, attempting refresh") - - refreshToken := getRefreshTokenFromCookie(c, config) - if refreshToken == "" { - config.GetAuthLogger().Debug("No refresh token found, redirecting to login") - clearTokenCookies(c, config) - return c.JSON(http.StatusUnauthorized, map[string]interface{}{ - "error": "Session expired", - "message": "Your session has expired. Please log in again.", - "login_url": config.GetAuthLoginPath(), - }) - } - - // Attempt to refresh tokens - newTokens, err := refreshTokens(refreshToken, config) - if err != nil { - config.GetAuthLogger().Warn("Token refresh failed", "error", err) - clearTokenCookies(c, config) - return c.JSON(http.StatusUnauthorized, map[string]interface{}{ - "error": "Session expired", - "message": "Unable to refresh your session. Please log in again.", - "login_url": config.GetAuthLoginPath(), - }) - } - - // Handle non-rotating refresh tokens - preserve original if new one is empty - if newTokens.RefreshToken == "" { - config.GetAuthLogger().Debug("Refresh response missing refresh token, preserving original") - newTokens.RefreshToken = refreshToken - } - - // Update cookies with new tokens - newExpiresAt := time.Now().Add(time.Duration(newTokens.ExpiresIn) * time.Second) - setTokenCookies(c, newTokens, newExpiresAt, config) - - // Update the token string for subsequent verification - tokenStr = newTokens.AccessToken - - // Update the Authorization header for this request - c.Request().Header.Set("Authorization", "Bearer "+tokenStr) - - config.GetAuthLogger().Info("Token refreshed successfully") - } - - // Get JWKS (skip in test mode) - var keySet jwk.Set - if !config.GetNoJWTValidation() { - keySet, err = GetJWKS(config.GetAuthJwksURL(), config.GetAuthLogger()) - if err != nil { - config.GetAuthLogger().Error("Failed to get JWKS", "error", err) - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to verify token"}) - } - } - - // Verify token (keySet can be nil in test mode) - claims, err := verifyToken(tokenStr, keySet, config) + // Verify token and populate context + _, err = verifyTokenAndSetContext(c, tokenStr, config) if err != nil { - config.GetAuthLogger().Warn("Token verification failed", "error", err) - return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"}) + if httpErr, ok := err.(*HTTPError); ok { + return c.JSON(httpErr.StatusCode, httpErr.Response) + } + return err } - // Store user info in context - c.Set("user_claims", claims) - userInfo := ExtractUserInfo(claims) - c.Set("user_info", userInfo) - // Perform Permit.io authorization if err := performPermitIOAuthorization(c, requestPath, config); err != nil { return err