76 lines
1.9 KiB
Go
76 lines
1.9 KiB
Go
package cognitoauth
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
)
|
|
|
|
// GetJWKS returns the cached JWKS or fetches a new one if needed
|
|
// This function implements caching logic to minimize external requests
|
|
// Used during token validation to get the key set for signature verification
|
|
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(24 * time.Hour)
|
|
|
|
return keySet, nil
|
|
}
|
|
|
|
// fetchJWKS retrieves the JSON Web Key Set from Cognito
|
|
// Used to get the public keys needed to verify token signatures
|
|
// Makes an HTTP request to the JWKS URL and returns the raw JSON
|
|
func fetchJWKS(jwksURL string) (string, error) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
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
|
|
}
|