Skip to content

Support for Placeholder Integration on JWKURL#96

Merged
ggicci merged 3 commits intoggicci:mainfrom
Cafeine42:main
Jun 3, 2025
Merged

Support for Placeholder Integration on JWKURL#96
ggicci merged 3 commits intoggicci:mainfrom
Cafeine42:main

Conversation

@Cafeine42
Copy link

@Cafeine42 Cafeine42 commented Apr 30, 2025

For #95

Exemple of use case in Caddyfile :

        map {host} {app.jwk_url} {
            demo.localhost {$KEYCLOAK_URL}/realms/demo/protocol/openid-connect/certs
            default {$KEYCLOAK_URL}/realms/test/protocol/openid-connect/certs
        }
        log_append debug {app.jwk_url}
        jwtauth {
            jwk_url {app.jwk_url}
        }

go.mod Outdated
@@ -1,4 +1,4 @@
module github.com/ggicci/caddy-jwt
module github.com/Cafeine42/caddy-jwt
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To drop

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. You don't have to change the module path if you do this in Git:

# clone the repo
git clone git@github.com:ggicci/httpin.git

# rename origin to upstream
git remote rename origin upstream

# add your forked repo as the origin remote
git remote add origin git@github.com:Cafeine42/httpin.git

# make some changes to the source code and commit to origin (your own repo) and open PR on GitHub
git add ...
git commit ...
git push origin ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this error when i try to install my version, it's why i changed this line.

RUN apk add --no-cache git &&
xcaddy build
--output /usr/local/bin/frankenphp
...
# Auth module
--with github.com/Cafeine42/caddy-jwt@v0.0.10

11.73 2025/05/27 09:45:51 [INFO] exec (timeout=0s): /usr/local/go/bin/go get -v -ldflags -w -s -extldflags '-Wl,-z,stack-size=0x80000' github.com/Cafeine42/caddy-jwt@v0.0.9 github.com/caddyserver/caddy/v2
15.89 go: downloading github.com/Cafeine42/caddy-jwt v0.0.9
21.22 go: github.com/Cafeine42/caddy-jwt@v0.0.9 found: parsing go.mod:
21.22 module declares its path as: github.com/ggicci/caddy-jwt
21.22 but was required as: github.com/Cafeine42/caddy-jwt
21.22 go: github.com/Cafeine42/caddy-jwt@v0.0.9 requires github.com/Cafeine42/caddy-jwt@v0.0.9: parsing go.mod:
21.22 module declares its path as: github.com/ggicci/caddy-jwt
21.22 but was required as: github.com/Cafeine42/caddy-jwt
21.22 2025/05/27 09:46:00 [FATAL] exit status 1

@codecov-commenter
Copy link

codecov-commenter commented Apr 30, 2025

Codecov Report

Attention: Patch coverage is 70.51282% with 23 lines in your changes missing coverage. Please review.

Project coverage is 90.09%. Comparing base (a248851) to head (3ce36e8).

Files with missing lines Patch % Lines
jwt.go 70.51% 16 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #96      +/-   ##
==========================================
- Coverage   95.10%   90.09%   -5.01%     
==========================================
  Files           2        2              
  Lines         347      404      +57     
==========================================
+ Hits          330      364      +34     
- Misses         12       28      +16     
- Partials        5       12       +7     
Flag Coverage Δ
unittests 90.09% <70.51%> (-5.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ggicci
Copy link
Owner

ggicci commented May 8, 2025

Thank you @Cafeine42 very much for the contribution! I will take a look this weekend and aim to merge and release as well.

jwt.go Outdated
jwkCache *jwk.Cache
jwkCachedSet jwk.Set
// JWK cache by resolved URL to support placeholders in JWKURL
jwkCaches map[string]*struct {
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the keys are populated during requests handling, I believe it should be guarded with a sync.RWMutex to allow concurrent access safely. No?

jwt.go Outdated
}

func (ja *JWTAuth) setupJWKLoader() {
// Initialiser le cache pour toutes les URL possibles
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, French is beautiful, but let's use English here 😆

jwt.go Outdated
cache *jwk.Cache
cachedSet jwk.Set
})
ja.logger.Info("JWK cache initialized for placeholder URL", zap.String("placeholder_url", ja.JWKURL))
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it have to be a placeholder URL here? Looks like the wording is a little misleading as both a static JWK URL and a placeholder URL will go this code path.

jwt.go Outdated
Comment on lines +306 to +309
// Resolve JWKURL with placeholders
replacer := request.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
resolvedURL := replacer.ReplaceAll(ja.JWKURL, "")

Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even there're only two lines of code here, I would prefer creating a separate function to get the JWK URL which is bound to the given HTTP request.

jwt.go Outdated
Comment on lines +310 to +311
ja.logger.Info("JWK unresolved", zap.String("placeholder_url", ja.JWKURL))
ja.logger.Info("JWK resolved", zap.String("placeholder_url", resolvedURL))
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to merge them as one.

// Replace placeholders in the signKey such as {file./path/to/sign_key.txt}
signKey = repl.ReplaceAll(signKey, "")
if len(signKey) == 0 {
resolvedSignKey := repl.ReplaceAll(signKey, "")
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice renaming, thx!

jwt.go Outdated
Comment on lines +155 to +156
cache *jwk.Cache
cachedSet jwk.Set
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, shall we create a struct type for the caches? I saw it has been used multiple times in the code later.

type jwkCacheEntry struct {
  URL String
  Cache *jwk.Cache
  CachedSet jwk.Set
}

jwt.go Outdated
Comment on lines +195 to +196
cache *jwk.Cache
cachedSet jwk.Set
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please simplify this by using a new type, which was requested in my comments above.

Comment on lines +215 to +220
// Try to refresh the cache immediately
_, err = cache.Refresh(context.Background(), resolvedURL)
if err != nil {
ja.logger.Warn("failed to refresh JWK cache during initialization", zap.Error(err), zap.String("url", resolvedURL))
// Continue even in case of error, the important thing is that the URL is registered
}
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is a repeat of refreshJWKCache(resolvedURL). I can imagine why you didn't call refreshJWKCache here was that it creates recusive calls.

Creating fine grained functions can avoid this. For example:

refreshJWKCache(entry, resolvedURL)

// or even better when we have the new type for the entry
entry.refresh() // entry: of jwkCacheEntry

jwt.go Outdated
Comment on lines +240 to +243
entry, err := ja.getOrCreateJWKCache(resolvedURL)
if err != nil {
return err
}
Copy link
Owner

@ggicci ggicci May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we want to reuse the refresh logic in getOrCreateJWKCache(), let's don't call getOrCreateJWKCache() here. The function should have less dependencies. I would suggest encapsulating it in a method of the proposed jwkCacheEntry struct above.

Copy link
Owner

@ggicci ggicci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @Cafeine42, thanks for the contribution. The code logic looks good to me. I left a bunch of comments there, basically all for code simplifying. Please let me know if you want my help on the update in case you don't have much time.

@Cafeine42 Cafeine42 force-pushed the main branch 2 times, most recently from de67265 to 03b05a6 Compare May 27, 2025 09:46
@Cafeine42
Copy link
Author

@ggicci Thanks for the review ! I’ve implemented the suggested changes, they help simplify the code. I wasn’t able to test everything thoroughly on my project though, so let me know if you run into any issues or if there’s anything else to tweak. I might test it more extensively next week.

Copy link
Owner

@ggicci ggicci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thx :)

@ggicci
Copy link
Owner

ggicci commented May 30, 2025

Too late now, I will merge and add docs and then do the release soon. Again, thanks very much for the feature implementation :)

@ggicci ggicci merged commit a0761d8 into ggicci:main Jun 3, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants