@@ -30,6 +30,23 @@ func init() {
3030type User = caddyauth.User
3131type Token = jwt.Token
3232
33+ // jwkCacheEntry stores the JWK cache information for a specific URL
34+ type jwkCacheEntry struct {
35+ URL string
36+ Cache * jwk.Cache
37+ CachedSet jwk.Set
38+ }
39+
40+ // refresh refreshes the JWK cache for this entry
41+ func (entry * jwkCacheEntry ) refresh (ctx context.Context , logger * zap.Logger ) error {
42+ _ , err := entry .Cache .Refresh (ctx , entry .URL )
43+ if err != nil {
44+ logger .Warn ("failed to refresh JWK cache" , zap .Error (err ), zap .String ("url" , entry .URL ))
45+ return err
46+ }
47+ return nil
48+ }
49+
3350// JWTAuth facilitates JWT (JSON Web Token) authentication.
3451type JWTAuth struct {
3552 // SignKey is the key used by the signing algorithm to verify the signature.
@@ -151,10 +168,7 @@ type JWTAuth struct {
151168 parsedSignKey interface {} // can be []byte, *rsa.PublicKey, *ecdsa.PublicKey, etc.
152169
153170 // JWK cache by resolved URL to support placeholders in JWKURL
154- jwkCaches map [string ]* struct {
155- cache * jwk.Cache
156- cachedSet jwk.Set
157- }
171+ jwkCaches map [string ]* jwkCacheEntry
158172}
159173
160174// CaddyModule implements caddy.Module interface.
@@ -182,19 +196,13 @@ func (ja *JWTAuth) usingJWK() bool {
182196}
183197
184198func (ja * JWTAuth ) setupJWKLoader () {
185- // Initialiser le cache pour toutes les URL possibles
186- ja .jwkCaches = make (map [string ]* struct {
187- cache * jwk.Cache
188- cachedSet jwk.Set
189- })
190- ja .logger .Info ("JWK cache initialized for placeholder URL" , zap .String ("placeholder_url" , ja .JWKURL ))
199+ // Initialize cache for all possible URLs
200+ ja .jwkCaches = make (map [string ]* jwkCacheEntry )
201+ ja .logger .Info ("JWK cache initialized for JWK URL" , zap .String ("jwk_url" , ja .JWKURL ))
191202}
192203
193204// getOrCreateJWKCache retrieves or creates a cache for a specific JWK URL
194- func (ja * JWTAuth ) getOrCreateJWKCache (resolvedURL string ) (* struct {
195- cache * jwk.Cache
196- cachedSet jwk.Set
197- }, error ) {
205+ func (ja * JWTAuth ) getOrCreateJWKCache (resolvedURL string ) (* jwkCacheEntry , error ) {
198206 // If the URL is empty, return an error
199207 if resolvedURL == "" {
200208 return nil , fmt .Errorf ("resolved JWK URL is empty" )
@@ -212,44 +220,27 @@ func (ja *JWTAuth) getOrCreateJWKCache(resolvedURL string) (*struct {
212220 return nil , fmt .Errorf ("failed to register JWK URL: %w" , err )
213221 }
214222
223+ // Create cache entry before attempting refresh
224+ entry := & jwkCacheEntry {
225+ URL : resolvedURL ,
226+ Cache : cache ,
227+ CachedSet : jwk .NewCachedSet (cache , resolvedURL ),
228+ }
229+
215230 // Try to refresh the cache immediately
216- _ , err = cache . Refresh (context .Background (), resolvedURL )
231+ err = entry . refresh (context .Background (), ja . logger )
217232 if err != nil {
218233 ja .logger .Warn ("failed to refresh JWK cache during initialization" , zap .Error (err ), zap .String ("url" , resolvedURL ))
219234 // Continue even in case of error, the important thing is that the URL is registered
220235 }
221236
222- cachedSet := jwk .NewCachedSet (cache , resolvedURL )
223- entry := & struct {
224- cache * jwk.Cache
225- cachedSet jwk.Set
226- }{
227- cache : cache ,
228- cachedSet : cachedSet ,
229- }
230-
231237 // Register the entry in the cache
232238 ja .jwkCaches [resolvedURL ] = entry
233- ja .logger .Info ("new JWK cache created" , zap .String ("url" , resolvedURL ), zap .Int ("loaded_keys" , cachedSet .Len ()))
239+ ja .logger .Info ("new JWK cache created" , zap .String ("url" , resolvedURL ), zap .Int ("loaded_keys" , entry . CachedSet .Len ()))
234240
235241 return entry , nil
236242}
237243
238- // refreshJWKCache refreshes the JWK cache for a specific URL. It validates the JWKs from the given URL.
239- func (ja * JWTAuth ) refreshJWKCache (resolvedURL string ) error {
240- entry , err := ja .getOrCreateJWKCache (resolvedURL )
241- if err != nil {
242- return err
243- }
244-
245- _ , err = entry .cache .Refresh (context .Background (), resolvedURL )
246- if err != nil {
247- ja .logger .Warn ("failed to refresh JWK cache" , zap .Error (err ), zap .String ("url" , resolvedURL ))
248- return err
249- }
250- return nil
251- }
252-
253244// Validate implements caddy.Validator interface.
254245func (ja * JWTAuth ) Validate () error {
255246 if ! ja .SkipVerification {
@@ -300,15 +291,18 @@ func (ja *JWTAuth) validateSignatureKeys() error {
300291 return nil
301292}
302293
294+ // resolveJWKURL prend une requête HTTP et résout l'URL JWK avec des espaces réservés
295+ func (ja * JWTAuth ) resolveJWKURL (request * http.Request ) string {
296+ replacer := request .Context ().Value (caddy .ReplacerCtxKey ).(* caddy.Replacer )
297+ return replacer .ReplaceAll (ja .JWKURL , "" )
298+ }
299+
303300func (ja * JWTAuth ) keyProvider (request * http.Request ) jws.KeyProviderFunc {
304- return func (context context.Context , sink jws.KeySink , sig * jws.Signature , _ * jws.Message ) error {
301+ return func (curContext context.Context , sink jws.KeySink , sig * jws.Signature , _ * jws.Message ) error {
305302 if ja .usingJWK () {
306- // Resolve JWKURL with placeholders
307- replacer := request .Context ().Value (caddy .ReplacerCtxKey ).(* caddy.Replacer )
308- resolvedURL := replacer .ReplaceAll (ja .JWKURL , "" )
303+ resolvedURL := ja .resolveJWKURL (request )
309304
310- ja .logger .Info ("JWK unresolved" , zap .String ("placeholder_url" , ja .JWKURL ))
311- ja .logger .Info ("JWK resolved" , zap .String ("placeholder_url" , resolvedURL ))
305+ ja .logger .Info ("JWK URL" , zap .String ("unresolved" , ja .JWKURL ), zap .String ("resolved" , resolvedURL ))
312306
313307 // Get or create the cache for this URL
314308 cacheEntry , err := ja .getOrCreateJWKCache (resolvedURL )
@@ -318,10 +312,10 @@ func (ja *JWTAuth) keyProvider(request *http.Request) jws.KeyProviderFunc {
318312
319313 // Use the key set associated with this URL
320314 kid := sig .ProtectedHeaders ().KeyID ()
321- key , found := cacheEntry .cachedSet .LookupKeyID (kid )
315+ key , found := cacheEntry .CachedSet .LookupKeyID (kid )
322316 if ! found {
323317 // Trigger an asynchronous refresh if the key is not found
324- go ja . refreshJWKCache ( resolvedURL )
318+ go cacheEntry . refresh ( context . Background (), ja . logger )
325319
326320 if kid == "" {
327321 return fmt .Errorf ("missing kid in JWT header" )
0 commit comments