Skip to content

Conversation

meher745
Copy link

@meher745 meher745 commented Aug 6, 2025

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

  1. discovery.go:
  • Fetches OIDC metadata from the provider’s discovery URL.
  • Caches metadata with configurable expiration to minimize network calls.
  1. oidc.go:
  • Middleware extracts the validated Bearer token (via Authorization header).
  • Calls the provider’s userinfo endpoint with the token and parses JSON user details.
  • Injects user info into the Go standard context.Context, retrievable by the helper GetOIDCUserInfo.

Usage Flow:

  • Fetch discovery metadata at app startup.
  • Pass discovered JWKS URI and issuer to app.EnableOAuth.
  • Register the OIDC userinfo middleware after OAuth.
  • Access user profile info in route handlers via context helper.

Testing

All new tests pass:

  • oidc_test.go covers userinfo middleware success, token absence, userinfo endpoint failures, and JSON parsing errors.
  • discovery_test.go covers metadata fetching success, caching, HTTP errors, JSON errors, and network failures.

Existing package tests:

  • All existing and new OIDC-related tests pass locally.
  • There is one unrelated failing legacy test (TestGetJwtClaims) from the GoFr core package that does NOT concern this PR and does not affect the OIDC middleware functionality.

Thanks for considering this contribution!

…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
@meher745
Copy link
Author

meher745 commented Aug 6, 2025

Hi, @Umang01-hash @coolwednesday . Please review this PR. Thank you so much!

return cachedMeta, nil
}

resp, err := http.Get(discoveryURL)
Copy link
Contributor

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
@meher745
Copy link
Author

Hi @ccoVeille @Umang01-hash @coolwednesday . I've updated the files according to the recommendations. Please review.

Copy link
Contributor

@ccoVeille ccoVeille left a 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

package version

const Framework = "dev"
const Framework = "v1.43.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this change?

Copy link
Author

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

Copy link
Member

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.

@meher745
Copy link
Author

@ccoVeille , kindly merge the pull request. Thanks!

@ccoVeille
Copy link
Contributor

I'm an active code reviewer of gofr. But I'm not a maintainer. I cannot merge.

Let's wait for a maintainer feedback

@coolwednesday @Umang01-hash

@akshat-kumar-singhal
Copy link
Contributor

@meher745 There's a lot of redundancy in your code right now - consider using the existing Auth Middleware which accepts AuthProvider.
cc @Umang01-hash @coolwednesday

Copy link
Member

@Umang01-hash Umang01-hash left a 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.

package version

const Framework = "dev"
const Framework = "v1.43.0"
Copy link
Member

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
Copy link
Member

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.

@Umang01-hash
Copy link
Member

@meher745 Are you still working in this PR?

@meher745
Copy link
Author

meher745 commented Sep 2, 2025

@meher745 Are you still working in this PR?

Yes I am. I am almost done with the desired changes.

@meher745 meher745 force-pushed the add-oidc-middleware branch from 3e04bb5 to 19458af Compare September 5, 2025 03:27
@meher745
Copy link
Author

meher745 commented Sep 5, 2025

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.

@meher745
Copy link
Author

@Umang01-hash @coolwednesday Please review, thanks.

@meher745
Copy link
Author

@ccoVeille @Umang01-hash @coolwednesday . Kindly review. Thanking you in advance!

Copy link
Contributor

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

Comment on lines +37 to +42
func NewDiscoveryCache(url string, cacheDuration time.Duration) *DiscoveryCache {
return &DiscoveryCache{
url: url,
cacheDuration: cacheDuration,
}
}
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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

Suggested change
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
Copy link
Contributor

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)
Copy link
Contributor

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'.

Comment on lines +34 to +44
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
}
Copy link
Contributor

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

Comment on lines +13 to +19
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")
)
Copy link
Contributor

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

@Umang01-hash
Copy link
Member

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!

@meher745
Copy link
Author

meher745 commented Oct 7, 2025

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.

@Umang01-hash
Copy link
Member

Hi @meher745,

Thanks for the update — no worries at all! Looking forward to your updated PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants