@@ -10,6 +10,7 @@ import (
10
10
"net/http"
11
11
"net/url"
12
12
"strings"
13
+ "sync"
13
14
"time"
14
15
15
16
"github.com/golang-jwt/jwt/v5"
@@ -57,7 +58,10 @@ type TokenValidator struct {
57
58
introspectURL string // Optional introspection endpoint
58
59
client * http.Client // HTTP client for making requests
59
60
60
- // No need for additional caching as jwk.Cache handles it
61
+ // Lazy JWKS registration
62
+ jwksRegistered bool
63
+ jwksRegistrationMu sync.Mutex
64
+ jwksRegistrationErr error
61
65
}
62
66
63
67
// TokenValidatorConfig contains configuration for the token validator.
@@ -176,7 +180,6 @@ func NewTokenValidator(ctx context.Context, config TokenValidatorConfig) (*Token
176
180
return nil , fmt .Errorf ("%w: %v" , ErrFailedToDiscoverOIDC , err )
177
181
}
178
182
jwksURL = doc .JWKSURI
179
-
180
183
}
181
184
182
185
// Ensure we have a JWKS URL either provided or discovered
@@ -203,11 +206,7 @@ func NewTokenValidator(ctx context.Context, config TokenValidatorConfig) (*Token
203
206
return nil , fmt .Errorf ("failed to create JWKS cache: %w" , err )
204
207
}
205
208
206
- // Register the JWKS URL with the cache
207
- err = cache .Register (ctx , jwksURL )
208
- if err != nil {
209
- return nil , fmt .Errorf ("failed to register JWKS URL: %w" , err )
210
- }
209
+ // Skip synchronous JWKS registration - will be done lazily on first use
211
210
212
211
return & TokenValidator {
213
212
issuer : config .Issuer ,
@@ -221,8 +220,40 @@ func NewTokenValidator(ctx context.Context, config TokenValidatorConfig) (*Token
221
220
}, nil
222
221
}
223
222
223
+ // ensureJWKSRegistered ensures that the JWKS URL is registered with the cache.
224
+ // This is called lazily on first use to avoid blocking startup.
225
+ func (v * TokenValidator ) ensureJWKSRegistered (ctx context.Context ) error {
226
+ v .jwksRegistrationMu .Lock ()
227
+ defer v .jwksRegistrationMu .Unlock ()
228
+
229
+ // Check if already registered or failed
230
+ if v .jwksRegistered {
231
+ return v .jwksRegistrationErr
232
+ }
233
+
234
+ // Create context with 5-second timeout for JWKS registration
235
+ registrationCtx , cancel := context .WithTimeout (ctx , 5 * time .Second )
236
+ defer cancel ()
237
+
238
+ // Attempt registration
239
+ err := v .jwksClient .Register (registrationCtx , v .jwksURL )
240
+ if err != nil {
241
+ v .jwksRegistrationErr = fmt .Errorf ("failed to register JWKS URL: %w" , err )
242
+ } else {
243
+ v .jwksRegistrationErr = nil
244
+ }
245
+
246
+ v .jwksRegistered = true
247
+ return v .jwksRegistrationErr
248
+ }
249
+
224
250
// getKeyFromJWKS gets the key from the JWKS.
225
251
func (v * TokenValidator ) getKeyFromJWKS (ctx context.Context , token * jwt.Token ) (interface {}, error ) {
252
+ // Ensure JWKS is registered before attempting to use it
253
+ if err := v .ensureJWKSRegistered (ctx ); err != nil {
254
+ return nil , fmt .Errorf ("JWKS registration failed: %w" , err )
255
+ }
256
+
226
257
// Validate the signing method
227
258
if _ , ok := token .Method .(* jwt.SigningMethodRSA ); ! ok {
228
259
return nil , fmt .Errorf ("unexpected signing method: %v" , token .Header ["alg" ])
0 commit comments