-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add OIDC middleware #2138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Add OIDC middleware #2138
Conversation
…fo support - Implement OIDC userinfo middleware to fetch and inject user profile - Add discovery helper to fetch OIDC provider metadata dynamically - Include comprehensive tests for userinfo middleware and discovery - Add detailed docs on usage and integration
Hi, @Umang01-hash @coolwednesday . Please review this PR. Thank you so much! |
return cachedMeta, nil | ||
} | ||
|
||
resp, err := http.Get(discoveryURL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And http.Get without passing a contexf doesn't sound a good idea
- Use strings.CutPrefix for token extraction - DiscoveryCache struct with per-URL cache/mutex - HTTP requests now use context - Updated tests for middleware and discovery - Updated docs for new usage patterns
Hi @ccoVeille @Umang01-hash @coolwednesday . I've updated the files according to the recommendations. Please review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sound good to me, a minor remark
pkg/gofr/version/version.go
Outdated
package version | ||
|
||
const Framework = "dev" | ||
const Framework = "v1.43.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, this change was not intentional. It seems it got included in my PR because I accidentally staged all changes using git add . while preparing my commit. Please let me know if you’d like me to remove it. @ccoVeille
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meher745 Please revert back this change.
@ccoVeille , kindly merge the pull request. Thanks! |
I'm an active code reviewer of gofr. But I'm not a maintainer. I cannot merge. Let's wait for a maintainer feedback |
@meher745 There's a lot of redundancy in your code right now - consider using the existing Auth Middleware which accepts AuthProvider. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meher745 Here are a few review suggestions for your PR:
- Instead of using dynamic errors (fmt.Errorf()) please use predefined errors.
- For the documentation i see we can improve on how we are portraying this feature to user, the code included should be well formatted, include proper spaces and comments. Remove repeteted things and big technical jagrons let's try to keep it simple so that evry user can understand it. Also the documentation file is directly placed under
docs/advanced-guide
which is not correct. We need to follow the exact same way other documentations are following. - Please resolve all the linter and code quality errors in your code.
pkg/gofr/version/version.go
Outdated
package version | ||
|
||
const Framework = "dev" | ||
const Framework = "v1.43.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meher745 Please revert back this change.
@@ -0,0 +1,76 @@ | |||
// File: pkg/gofr/http/middleware/oidc.go | |||
|
|||
package middleware |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To integrate OIDC with the existing AuthProvider
based AuthMiddleware, you can implement a new OIDCAuthProvider struct in oidc.go
that satisfies the AuthProvider interface. This allows you to reuse the unified authentication flow for OIDC, just like Basic, API key, and OAuth. Simply extract (& validate if needed) the Bearer token in ExtractAuthHeader, and return user info or claims as needed. Then, pass your provider to AuthMiddleware for consistent middleware usage.
@meher745 Are you still working in this PR? |
Yes I am. I am almost done with the desired changes. |
3e04bb5
to
19458af
Compare
Hi, @Umang01-hash @coolwednesday please let me know if the recent commits I made are fine. I'm kind of confused since I had to remove go.work.sum and go.sum, and build them again due to some import conflicts. |
@Umang01-hash @coolwednesday Please review, thanks. |
@ccoVeille @Umang01-hash @coolwednesday . Kindly review. Thanking you in advance! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get the version downgrade here
func NewDiscoveryCache(url string, cacheDuration time.Duration) *DiscoveryCache { | ||
return &DiscoveryCache{ | ||
url: url, | ||
cacheDuration: cacheDuration, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url variable name would shadow url package
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, dc.url, http.NoBody) | ||
if err != nil { | ||
return nil, ErrFailedCreateDiscoveryRequest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and everywhere
I would use errors.Join or fmt.Errorf("%w: %w") to keep the err in the stack
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return nil, ErrBadDiscoveryStatus |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning the status code in the error is always interesting
return nil, ErrBadDiscoveryStatus | |
return nil, fmt.Errorf("%w: unexpected status code: %d", ErrBadDiscoveryStatus, resp.StatusCode) |
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return nil, ErrUserInfoBadStatus |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here about returning the status code
return nil, ErrEmptyToken | ||
} | ||
|
||
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, p.UserInfoEndpoint, http.NoBody) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider injecting the HTTP client instead of creating a new one. Also, use existing constant for 'Authorization'.
authHeader := r.Header.Get("Authorization") | ||
|
||
token, ok := strings.CutPrefix(authHeader, "Bearer ") | ||
if !ok { | ||
return nil, ErrMissingToken | ||
} | ||
|
||
token = strings.TrimSpace(token) | ||
if token == "" { | ||
return nil, ErrEmptyToken | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is present in "getAuthHeaderFromRequest()" auth.go
ErrMissingToken = errors.New("missing bearer token") | ||
ErrEmptyToken = errors.New("empty bearer token") | ||
ErrCreateRequest = errors.New("failed to create userinfo request") | ||
ErrUserInfoFetch = errors.New("failed to fetch userinfo") | ||
ErrUserInfoBadStatus = errors.New("userinfo endpoint returned error status") | ||
ErrUserInfoJSON = errors.New("failed to parse userinfo response") | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid duplicating errors - some of these are already present
Hey @meher745 ! There are a few review comments on your PR that still need to be addressed. Just checking in — are you currently working on this? If so, let’s aim to wrap it up soon, as it’s been pending for a while and we’re quite close to completing it. If you need any help or clarification, feel free to reach out — happy to support however I can. Looking forward to your update! |
Hi @Umang01-hash . I have gove through the suggestions and started working on them. I apologise for the delay since I had my semester exams going on. Will get back to you with updated pr soon! Thanks in advance. |
Hi @meher745, Thanks for the update — no worries at all! Looking forward to your updated PR. |
This PR introduces comprehensive OpenID Connect (OIDC) support into the GoFr framework by adding:
OIDC Discovery Metadata Fetching & Caching (discovery.go)
Implements dynamic fetching and caching of OIDC provider metadata (including issuer, JWKS URI, and userinfo endpoint) from the standard .well-known/openid-configuration URL.
OIDC Userinfo Middleware (oidc.go)
Middleware to fetch user profile information from the OIDC userinfo endpoint after OAuth JWT token validation. This middleware injects the retrieved user info into the request context for downstream handlers to use.
Dedicated Tests (oidc_test.go, discovery_test.go)
Thorough test coverage for both userinfo middleware and discovery metadata fetching utilities, covering success scenarios, error handling, caching behavior, and edge cases.
Documentation (docs/advanced-guide/oidc.md)
Detailed usage guide explaining how to configure and use the new OIDC features alongside GoFr’s built-in OAuth middleware. Includes examples for discovery, middleware registration, and handler access to user info.
Implementation Highlights
Usage Flow:
Testing
All new tests pass:
Existing package tests:
Thanks for considering this contribution!