efe87321b2
Testing Tweaks * parallel * slowestquery * split * cleanup * health * dynamiccores * go * profile * timeout * commitmychanges * taskfile * sqlcheckstart * client * cost * ctxtimeout
128 lines
4.3 KiB
Go
128 lines
4.3 KiB
Go
package cognitoauth
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
)
|
|
|
|
// Time constants for JWKS operations
|
|
const (
|
|
// JWKS_CACHE_TTL is the time-to-live duration for the JWKS cache (24 hours)
|
|
JWKS_CACHE_TTL = 24 * time.Hour
|
|
|
|
// JWKS_CLIENT_TIMEOUT is the timeout duration for HTTP requests to fetch JWKS (10 seconds)
|
|
JWKS_CLIENT_TIMEOUT = 10 * time.Second
|
|
)
|
|
|
|
// GetJWKS retrieves a JSON Web Key Set (JWKS) from the specified URL, using a
|
|
// thread-safe caching mechanism to minimize external requests.
|
|
//
|
|
// It implements a double-checked locking pattern to efficiently handle concurrent
|
|
// access to the cached JWKS. It first attempts to retrieve a valid cached key set
|
|
// using a read lock. If the cache is empty or expired, it acquires a write lock
|
|
// to update the cache, but does a second check to avoid redundant fetches in case
|
|
// another goroutine has already updated the cache.
|
|
//
|
|
// The retrieved JWKS is cached for JWKS_CACHE_TTL duration (24 hours by default).
|
|
// This cache duration can be adjusted based on the security requirements and the
|
|
// key rotation policy of the identity provider.
|
|
//
|
|
// Parameters:
|
|
// - jwksURL: The URL from which to fetch the JWKS (typically provided by the Cognito or OAuth provider)
|
|
// - logger: A structured logger for recording debug and error information during the JWKS retrieval process
|
|
//
|
|
// Returns:
|
|
// - jwk.Set: The parsed JSON Web Key Set containing the public keys used for token verification
|
|
// - error: An error if the JWKS cannot be fetched or parsed, nil otherwise
|
|
//
|
|
// This function is primarily used during token validation to get the key set
|
|
// needed for signature verification of JWTs issued by the identity provider.
|
|
func GetJWKS(jwksURL string, logger *slog.Logger) (jwk.Set, error) {
|
|
// Try to use cached JWKS first (read lock)
|
|
jwksCache.mutex.RLock()
|
|
if jwksCache.KeySet != nil && time.Now().Before(jwksCache.ExpiresAt) {
|
|
defer jwksCache.mutex.RUnlock()
|
|
logger.Debug("Using cached JWKS")
|
|
return jwksCache.KeySet, nil
|
|
}
|
|
jwksCache.mutex.RUnlock()
|
|
|
|
// Need to fetch new JWKS (write lock)
|
|
jwksCache.mutex.Lock()
|
|
defer jwksCache.mutex.Unlock()
|
|
|
|
// Double-check expiration after acquiring the write lock
|
|
if jwksCache.KeySet != nil && time.Now().Before(jwksCache.ExpiresAt) {
|
|
return jwksCache.KeySet, nil
|
|
}
|
|
|
|
logger.Info("Fetching new JWKS", "url", jwksURL)
|
|
jwksJSON, err := fetchJWKS(jwksURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch JWKS: %w", err)
|
|
}
|
|
|
|
keySet, err := jwk.ParseString(jwksJSON)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse JWKS: %w", err)
|
|
}
|
|
|
|
jwksCache.KeySet = keySet
|
|
// Cache for 24 hours - you can adjust this based on your needs
|
|
jwksCache.ExpiresAt = time.Now().Add(JWKS_CACHE_TTL)
|
|
|
|
return keySet, nil
|
|
}
|
|
|
|
// fetchJWKS makes an HTTP request to retrieve the JSON Web Key Set (JWKS) from
|
|
// the specified URL, typically from a Cognito or other OAuth identity provider.
|
|
//
|
|
// This function handles the actual HTTP communication with the JWKS endpoint,
|
|
// using a timeout-configured HTTP client to ensure the request doesn't hang
|
|
// indefinitely. It performs a standard GET request and reads the raw JSON
|
|
// response containing the public keys.
|
|
//
|
|
// The HTTP client is configured with JWKS_CLIENT_TIMEOUT (10 seconds by default)
|
|
// to prevent long-running requests. This timeout can be adjusted based on network
|
|
// conditions and reliability requirements.
|
|
//
|
|
// Parameters:
|
|
// - jwksURL: The URL endpoint from which to fetch the JWKS
|
|
//
|
|
// Returns:
|
|
// - string: The raw JSON string containing the JWKS data if successful
|
|
// - error: An error if the HTTP request fails, the response cannot be read,
|
|
// or any other error occurs during the fetching process
|
|
//
|
|
// This function is typically called by GetJWKS when the cached JWKS has expired
|
|
// or is not available, and should not be called directly in most cases.
|
|
func fetchJWKS(jwksURL string) (string, error) {
|
|
return fetchJWKSWithTimeout(jwksURL, JWKS_CLIENT_TIMEOUT)
|
|
}
|
|
|
|
func fetchJWKSWithTimeout(jwksURL string, timeout time.Duration) (string, error) {
|
|
client := &http.Client{Timeout: timeout}
|
|
req, err := http.NewRequest("GET", jwksURL, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
jwksData, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(jwksData), nil
|
|
}
|