Skip to content

fix: process credentials when custom HTTPClient is provided#276

Open
SoulPancake wants to merge 4 commits intomainfrom
fix/creds-ignored-bug
Open

fix: process credentials when custom HTTPClient is provided#276
SoulPancake wants to merge 4 commits intomainfrom
fix/creds-ignored-bug

Conversation

@SoulPancake
Copy link
Member

@SoulPancake SoulPancake commented Feb 17, 2026

Description

For issue #234
Honors both the custom client and credentials

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • Bug Fixes

    • Credentials are now correctly processed when a custom HTTP client is provided, ensuring authentication headers and OAuth2 clients are applied as expected.
  • Tests

    • Added comprehensive tests validating credential handling across credential types and custom HTTP client scenarios.
  • Chores

    • Updated OpenTelemetry dependencies to v1.40.0
  • Documentation

    • Added an Unreleased changelog entry.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Decouples HTTP client defaulting from credentials processing, ensures credential-provided header overrides are always applied, allows OAuth2 to wrap a provided base HTTP client, adds tests for credential scenarios, and updates OpenTelemetry versions in example modules.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added Unreleased entry documenting fix: process credentials when custom HTTPClient is provided.
API client core
api_client.go
Default HTTPClient is set up before credential handling; credential processing now runs when cfg.Credentials != nil, always applies header overrides, and may replace the API client's HTTPClient when an OAuth2-enabled client is returned.
Credentials logic
credentials/credentials.go
GetHttpClientAndHeaderOverrides signature extended to accept baseClient *http.Client; uses a local ctx, injects oauth2.HTTPClient(baseClient) when provided so OAuth2 client can wrap/reuse the given transport, and returns client + header overrides accordingly.
Tests
api_client_test.go
Added TestApiClientWithCredentials with multiple subtests validating ApiToken and ClientCredentials behaviors with and without custom HTTPClient, asserting header overrides and OAuth2 wrapping/preservation semantics.
Example deps
example/example1/go.mod, example/opentelemetry/go.mod, example/streamed_list_objects/go.mod
Bumped OpenTelemetry modules (go.opentelemetry.io/otel, /metric, /trace) from v1.39.0 to v1.40.0 in example modules.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Test as Test / Client
participant API as ApiClient.Init
participant Cred as Credentials
participant OAuth as OAuth2 Library
participant Server as API Server

Test->>API: NewApiClient(cfg with custom HTTPClient, Credentials)
API->>API: Ensure default HTTPClient if cfg.HTTPClient nil
API->>Cred: GetHttpClientAndHeaderOverrides(retryParams, debug, baseClient=cfg.HTTPClient)
Cred->>Cred: use local ctx (or Background)
alt baseClient provided
    Cred->>OAuth: set oauth2.HTTPClient(ctx, baseClient)
    OAuth-->>Cred: OAuth2-enabled http.Client
else no baseClient
    Cred-->>Cred: return http.DefaultClient or header overrides
end
Cred-->>API: returns (maybe-wrapped-client, headerOverrides)
API->>API: apply headerOverrides to default headers
alt returned client != http.DefaultClient
    API->>API: replace API.HTTPClient with returned client
end
Test->>API: perform request
API->>Server: request with applied headers (Authorization if provided)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

  • Issue #234: addresses credentials handling when a custom HTTPClient is provided.

Suggested reviewers

  • ewanharris
  • rhamzeh
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: ensuring credentials are processed when a custom HTTPClient is provided, which is the core fix addressed in PR #276.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/creds-ignored-bug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SoulPancake SoulPancake marked this pull request as ready for review February 17, 2026 08:03
@SoulPancake SoulPancake requested a review from a team as a code owner February 17, 2026 08:03
Copilot AI review requested due to automatic review settings February 17, 2026 08:03
@codecov-commenter
Copy link

codecov-commenter commented Feb 17, 2026

Codecov Report

❌ Patch coverage is 58.82353% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 33.61%. Comparing base (d72fc41) to head (ac5a127).

Files with missing lines Patch % Lines
credentials/credentials.go 0.00% 7 Missing ⚠️

❌ Your project status has failed because the head coverage (33.61%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #276      +/-   ##
==========================================
+ Coverage   33.59%   33.61%   +0.01%     
==========================================
  Files         115      115              
  Lines        9810     9812       +2     
==========================================
+ Hits         3296     3298       +2     
  Misses       6252     6252              
  Partials      262      262              

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

@dosubot
Copy link

dosubot bot commented Feb 17, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

OpenFGA's Space

Custom HTTP Headers Support
View Suggested Changes
@@ -160,13 +160,11 @@
 [Source](https://github.com/openfga/go-sdk/blob/c52ca06c03934b954d50ca34845b73076b6fa9eb/api_client.go#L0-L500)
 
 #### Validation Rules and Reserved Headers
-The Go SDK does not explicitly document reserved headers or validation rules for headers, nor explicit injection prevention mechanisms.
-
----
+The Go SDK does not explicitly document a reserved header list or detailed validation rules for headers. However, authentication headers such as `Authorization` are always set internally by the SDK, regardless of whether a custom HTTPClient is provided. If you provide both a custom HTTPClient and use ClientCredentials authentication, the SDK will override your HTTPClient with an OAuth2-enabled client to ensure proper authentication. Attempts to set reserved headers like `Authorization` in your custom headers will be ignored in favor of the SDK-managed value. There are no explicit header injection prevention mechanisms documented, so avoid including newline characters in header values.
 
 ## Go SDK
+
 #### Calling Other Endpoints with APIExecutor
-
 The Go SDK provides an `APIExecutor` interface for making raw HTTP calls to any OpenFGA endpoint, including endpoints not yet wrapped by the SDK. This is useful if you need to:
 
 - Call a new or experimental endpoint not yet supported by the SDK
@@ -203,11 +201,10 @@
 ### Best Practices
 - Use global default headers for values that should be sent with every request (e.g., application identifiers, telemetry).
 - Use per-request headers for values that are specific to a single operation (e.g., request IDs, tracing).
-- Do not attempt to set reserved headers such as `Authorization` or `Content-Type` unless explicitly supported by the SDK.
+- Do not attempt to set reserved headers such as `Authorization` or `Content-Type`; these are always set by the SDK as needed for authentication and protocol compliance.
+- If you provide both a custom HTTPClient and use ClientCredentials authentication, be aware that the SDK will override your HTTPClient with an OAuth2-enabled client to ensure proper authentication. For custom transport settings with OAuth2, configure the transport through the OAuth2 client.
 - Avoid including newline characters in header values to prevent header injection vulnerabilities.
 - Review SDK documentation and source for updates on reserved headers and validation rules, especially if you need to set headers that may conflict with protocol or SDK-managed headers.
-
----
 
 ### Comparison Table
 | SDK     | Global Headers Config | Per-Request Headers | Header Merging | Reserved Headers | Injection Prevention |

[Accept] [Decline]

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
api_client_test.go (2)

32-89: Consider extracting shared mock setup into a test helper.

The httpmock registration and response construction are nearly identical between "ApiToken credentials should be applied with custom HTTPClient" and "Credentials should work when HTTPClient is nil". A small helper (e.g., registerAuthModelResponder) would reduce duplication.

Also applies to: 120-172

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api_client_test.go` around lines 32 - 89, The two tests ("ApiToken
credentials should be applied with custom HTTPClient" and "Credentials should
work when HTTPClient is nil") duplicate the httpmock.RegisterResponder and JSON
response construction; extract that into a helper function (e.g.,
registerAuthModelResponder) that accepts the expected URL or storeID and
returns/sets the responder for apiClient.OpenFgaApi.ReadAuthorizationModels
calls, builds the ReadAuthorizationModelsResponse payload, and exposes a way to
assert the Authorization header was observed; replace the duplicated
registration blocks in both tests with calls to this helper to reduce
duplication and centralize responder logic.

91-118: Test doesn't verify the OAuth2 client is functional — only that the pointer changed.

The assertion configuration.HTTPClient == customHTTPClient confirms replacement but doesn't validate that the new client has the OAuth2 transport configured. Consider adding an assertion on the transport type (e.g., checking that the client's transport is an oauth2.Transport).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api_client_test.go` around lines 91 - 118, The test currently only checks
that configuration.HTTPClient != customHTTPClient; update the test
"ClientCredentials should be processed with custom HTTPClient" to also verify
the new HTTP client uses an OAuth2 transport by asserting the client's Transport
(configuration.HTTPClient.Transport) is of type *oauth2.Transport (or wraps it),
and ensure the oauth2 package is imported; use NewAPIClient or the resulting
configuration to obtain the HTTPClient and fail the test if the transport is not
an oauth2.Transport.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@example/opentelemetry/go.mod`:
- Around line 13-16: Dependency versions for OpenTelemetry modules are
inconsistent: update
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc,
go.opentelemetry.io/otel/sdk, and go.opentelemetry.io/otel/sdk/metric to v1.40.0
to match go.opentelemetry.io/otel v1.40.0; locate those module lines in go.mod
and change their versions from v1.39.0 to v1.40.0 so all otel modules are
aligned.

---

Nitpick comments:
In `@api_client_test.go`:
- Around line 32-89: The two tests ("ApiToken credentials should be applied with
custom HTTPClient" and "Credentials should work when HTTPClient is nil")
duplicate the httpmock.RegisterResponder and JSON response construction; extract
that into a helper function (e.g., registerAuthModelResponder) that accepts the
expected URL or storeID and returns/sets the responder for
apiClient.OpenFgaApi.ReadAuthorizationModels calls, builds the
ReadAuthorizationModelsResponse payload, and exposes a way to assert the
Authorization header was observed; replace the duplicated registration blocks in
both tests with calls to this helper to reduce duplication and centralize
responder logic.
- Around line 91-118: The test currently only checks that
configuration.HTTPClient != customHTTPClient; update the test "ClientCredentials
should be processed with custom HTTPClient" to also verify the new HTTP client
uses an OAuth2 transport by asserting the client's Transport
(configuration.HTTPClient.Transport) is of type *oauth2.Transport (or wraps it),
and ensure the oauth2 package is imported; use NewAPIClient or the resulting
configuration to obtain the HTTPClient and fail the test if the transport is not
an oauth2.Transport.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR attempts to fix issue #234 where credentials were being ignored when a custom HTTPClient was provided. The original bug was that credentials processing was only done when cfg.HTTPClient == nil, preventing users from combining custom HTTP client settings (like timeouts, transports) with authentication.

Changes:

  • Refactored credential processing to always execute regardless of whether a custom HTTPClient is provided
  • Added logic to handle conflicts between custom HTTPClient and OAuth2-enabled clients for ClientCredentials
  • Added comprehensive test coverage for ApiToken and ClientCredentials authentication with custom HTTPClient
  • Updated OpenTelemetry dependencies from v1.39.0 to v1.40.0 in example modules

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

File Description
api_client.go Restructured NewAPIClient to always process credentials and handle HTTPClient conflicts
api_client_test.go Added tests for ApiToken and ClientCredentials with custom HTTPClient scenarios
CHANGELOG.md Added entry for the fix
example/*/go.mod Updated OpenTelemetry dependencies to v1.40.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@SoulPancake
Copy link
Member Author

Actually, Instead of replacing the custom HTTPClient with an OAuth2 client, we can inject the custom client into the OAuth2 context essentially honoring both

@SoulPancake
Copy link
Member Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
credentials/credentials.go (1)

104-148: Sentinel-value coupling between GetHttpClientAndHeaderOverrides and its caller is fragile.

Returning http.DefaultClient as a sentinel to signal "no OAuth2 client was created" (line 106) works because the caller in api_client.go (line 82) checks httpClient != http.DefaultClient. However, this creates an implicit contract: if anyone changes the default on either side, the logic silently breaks.

Consider returning nil when no OAuth2 client is produced (for ApiToken/None branches) and having the caller check for nil instead. This makes the "no client override" semantic explicit.

Proposed diff
 func (c *Credentials) GetHttpClientAndHeaderOverrides(retryParams retryutils.RetryParams, debug bool, baseClient *http.Client) (*http.Client, []*HeaderParams) {
 	var headers []*HeaderParams
-	var client = http.DefaultClient
+	var client *http.Client
 	switch c.Method {
 	case CredentialsMethodClientCredentials:
         ...

And in api_client.go:

-		if httpClient != nil && httpClient != http.DefaultClient {
+		if httpClient != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@credentials/credentials.go` around lines 104 - 148, The function
GetHttpClientAndHeaderOverrides currently uses http.DefaultClient as a sentinel
to indicate "no OAuth2 client"; change this to return nil when no OAuth2 client
is created (i.e., for CredentialsMethodApiToken and CredentialsMethodNone) and
only set the client variable for the CredentialsMethodClientCredentials branch
(where you call ccConfig.Client(ctx)); then update the caller in api_client.go
to check for a nil httpClient instead of comparing to http.DefaultClient so the
"no client override" semantic is explicit and not tied to the default client
value.
api_client_test.go (2)

227-280: Same observation: good scenario but could assert transport chain integrity.

This test sets an explicit customTransport (line 230–234) and is the ideal place to verify the OAuth2 client actually wraps it, since the custom transport is non-nil here. Without that assertion, the test only proves the pointer changed, not that the fix is functioning as intended.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api_client_test.go` around lines 227 - 280, Add an assertion to verify the
OAuth2 client actually wraps the provided customTransport (not just that the
HTTPClient pointer changed). After creating configuration via NewConfiguration
and apiClient via NewAPIClient, unwrap configuration.HTTPClient/
apiClient.GetConfig().HTTPClient.Transport (e.g., type-assert to
*oauth2.Transport and check its Base field, or iterate RoundTripper wrappers)
and assert that the final/base transport equals customTransport; fail the test
if the wrapped/base transport is not customTransport. Use symbols
NewConfiguration, NewAPIClient, apiClient.GetConfig().HTTPClient, and
customTransport to locate where to add this check.

96-134: ClientCredentials wrapping test only verifies pointer inequality, not that the custom transport is actually in the chain.

The test confirms the HTTP client was replaced but doesn't verify the custom client's transport is preserved as the base of the OAuth2 transport chain. Consider asserting that the resulting client's transport's Base is the custom transport — this would confirm the wrapping behavior that the PR aims to guarantee.

Example assertion (after existing checks)
// Verify the custom transport is wrapped inside the OAuth2 transport
if oauthTransport, ok := configuration.HTTPClient.Transport.(*oauth2.Transport); ok {
    if oauthTransport.Base != customHTTPClient.Transport {
        // For nil transport, Base may also be nil (defaults to http.DefaultTransport)
        if customHTTPClient.Transport != nil {
            t.Error("OAuth2 transport should wrap the custom client's transport")
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api_client_test.go` around lines 96 - 134, Update the test that constructs
Configuration via NewConfiguration (using the customHTTPClient) to assert that
the OAuth2 transport actually wraps the custom client's transport: after
creating apiClient and verifying configuration.HTTPClient changed, cast
configuration.HTTPClient.Transport to *oauth2.Transport (reference
oauthTransport) and check that oauthTransport.Base equals
customHTTPClient.Transport, accounting for the case where
customHTTPClient.Transport is nil (which implies Base may be nil or
http.DefaultTransport); if the wrapped Base does not match the custom transport
(and the custom transport was non-nil) fail the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@api_client_test.go`:
- Around line 227-280: Add an assertion to verify the OAuth2 client actually
wraps the provided customTransport (not just that the HTTPClient pointer
changed). After creating configuration via NewConfiguration and apiClient via
NewAPIClient, unwrap configuration.HTTPClient/
apiClient.GetConfig().HTTPClient.Transport (e.g., type-assert to
*oauth2.Transport and check its Base field, or iterate RoundTripper wrappers)
and assert that the final/base transport equals customTransport; fail the test
if the wrapped/base transport is not customTransport. Use symbols
NewConfiguration, NewAPIClient, apiClient.GetConfig().HTTPClient, and
customTransport to locate where to add this check.
- Around line 96-134: Update the test that constructs Configuration via
NewConfiguration (using the customHTTPClient) to assert that the OAuth2
transport actually wraps the custom client's transport: after creating apiClient
and verifying configuration.HTTPClient changed, cast
configuration.HTTPClient.Transport to *oauth2.Transport (reference
oauthTransport) and check that oauthTransport.Base equals
customHTTPClient.Transport, accounting for the case where
customHTTPClient.Transport is nil (which implies Base may be nil or
http.DefaultTransport); if the wrapped Base does not match the custom transport
(and the custom transport was non-nil) fail the test.

In `@credentials/credentials.go`:
- Around line 104-148: The function GetHttpClientAndHeaderOverrides currently
uses http.DefaultClient as a sentinel to indicate "no OAuth2 client"; change
this to return nil when no OAuth2 client is created (i.e., for
CredentialsMethodApiToken and CredentialsMethodNone) and only set the client
variable for the CredentialsMethodClientCredentials branch (where you call
ccConfig.Client(ctx)); then update the caller in api_client.go to check for a
nil httpClient instead of comparing to http.DefaultClient so the "no client
override" semantic is explicit and not tied to the default client value.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants