Skip to content

fix(sdk): normalize issuer URL before OIDC discovery#3261

Merged
damorris25 merged 1 commit intoopentdf:mainfrom
damorris25:fix/oidc-discovery-trailing-slash
Apr 6, 2026
Merged

fix(sdk): normalize issuer URL before OIDC discovery#3261
damorris25 merged 1 commit intoopentdf:mainfrom
damorris25:fix/oidc-discovery-trailing-slash

Conversation

@damorris25
Copy link
Copy Markdown
Member

@damorris25 damorris25 commented Apr 6, 2026

Summary

Use url.JoinPath instead of string concatenation when constructing OIDC discovery URLs from the issuer. This prevents double-slash URLs when the issuer has a trailing slash (e.g., Authentik).

Problem

IDPs like Authentik include a trailing slash in their OIDC issuer URL (e.g., https://idp.example/app/). Two places in the codebase concatenate the issuer with /.well-known/openid-configuration, producing a double-slash URL:

https://idp.example/app//.well-known/openid-configuration
  • sdk/sdk.go:456 — SDK's OIDC discovery for otdfctl/SDK consumers. Uses SafeHTTPClient which does not follow redirects, so the 301 redirect (empty body) causes json.Unmarshal to fail with unexpected end of JSON input. This is the user-facing bug.
  • service/internal/auth/discovery.go:35 — Platform's own OIDC discovery. Uses http.DefaultClient which follows redirects, so it works but makes an unnecessary redirect round-trip.

Fix

Replace manual string concatenation with url.JoinPath (Go 1.19+), which handles path joining with proper slash normalization:

// Before
oidcConfigURL := issuerURL + "/.well-known/openid-configuration"

// After
oidcConfigURL, err := url.JoinPath(issuerURL, ".well-known/openid-configuration")

url.JoinPath is a no-op when the issuer has no trailing slash (Keycloak), so this is backwards-compatible.

Fixes #3260

Test plan

  • Existing tests pass (go test ./sdk/... and go test ./service/...)
  • Verify with Authentik: otdfctl --with-access-token succeeds against a trailing-slash issuer
  • Verify with Keycloak: no regression (issuer without trailing slash unchanged)

🤖 Generated with Claude Code

@damorris25 damorris25 requested review from a team as code owners April 6, 2026 13:48
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where OIDC discovery fails for identity providers that include a trailing slash in their issuer URL. By ensuring the URL is properly formatted before appending the discovery path, the SDK avoids generating invalid request URLs that lead to 301 redirects and subsequent parsing errors.

Highlights

  • OIDC Discovery Fix: Normalized the issuer URL by stripping trailing slashes to prevent malformed discovery endpoint requests.
  • Compatibility: Ensured the fix remains backwards-compatible for identity providers that do not include a trailing slash.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


The slash at the end of the line, Caused errors that made us repine. With a trim of the string, We fix the whole thing, And make the discovery shine.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the comp:sdk A software development kit, including library, for client applications and inter-service communicati label Apr 6, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

The getTokenEndpoint function in the SDK now removes trailing slashes from the platform issuer URL before constructing the OIDC discovery endpoint path, eliminating malformed URLs with double slashes that would cause discovery requests to fail.

Changes

Cohort / File(s) Summary
OIDC Discovery URL Normalization
sdk/sdk.go
Added strings.TrimRight() to remove trailing / from platform_issuer before appending /.well-known/openid-configuration, preventing double-slash URLs when the issuer has a trailing slash.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A slash, a slash, was doubled up—
Two forward friends, a tangled cup,
One TrimRight to set things straight,
Now URLs find their proper gate,
The rabbit grins, the path runs clean! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: normalizing the issuer URL by removing trailing slashes before OIDC discovery URL construction.
Linked Issues check ✅ Passed The pull request implements Option A from issue #3260 by using strings.TrimRight to normalize the issuer URL before OIDC discovery, fully addressing the core objective.
Out of Scope Changes check ✅ Passed The change is minimal and focused: only the OIDC discovery URL construction is modified, with no extraneous alterations to other logic or unrelated code.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the OIDC configuration URL construction in sdk/sdk.go to correctly handle trailing slashes in the issuer URL using strings.TrimRight. Feedback suggests using url.JoinPath for a more idiomatic and robust implementation, and recommends unifying this discovery logic into a shared package to address duplication and potential inconsistencies across the codebase.

Comment thread sdk/sdk.go Outdated
Comment thread sdk/sdk.go Outdated
Copy link
Copy Markdown

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sdk/sdk.go (2)

449-489: ⚠️ Potential issue | 🟠 Major

Add unit tests for issuer normalization behavior.

This SDK behavior changed, but no corresponding test is shown for trailing-slash and non-trailing-slash issuers. Please add coverage for both forms to prevent regressions.

As per coding guidelines, "Run go test ./... or make test and ensure all existing tests pass; add tests for new functionality".

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

In `@sdk/sdk.go` around lines 449 - 489, Add unit tests for getTokenEndpoint to
verify issuer normalization with both trailing and non-trailing slashes: create
two table-driven test cases supplying config objects whose
PlatformConfiguration["platform_issuer"] is "http://example.org" and
"http://example.org/" respectively, start an httptest.Server that serves a
/.well-known/openid-configuration JSON including
"token_endpoint":"http://example.org/token", and set c.httpClient (or inject the
test server URL via the client Transport) so getTokenEndpoint uses the test
server; assert the returned token endpoint matches the expected value and that
no error is returned to prevent regressions in issuer trimming.

467-481: ⚠️ Potential issue | 🟠 Major

Check HTTP response status before unmarshaling JSON in OIDC discovery.

The code calls client.Do(req) and checks only for the error return value, but does not validate resp.StatusCode. Non-2xx responses (3xx/4xx/5xx) will fall through to json.Unmarshal, which fails with a misleading JSON parsing error instead of revealing the actual HTTP failure. Add a status code check immediately after reading the response body:

Suggested fix
 resp, err := client.Do(req)
 if err != nil {
 	return "", err
 }
 defer resp.Body.Close()

 body, err := io.ReadAll(resp.Body)
 if err != nil {
 	return "", err
 }
+if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+	return "", fmt.Errorf("oidc discovery failed: status=%d url=%s body=%q", resp.StatusCode, oidcConfigURL, string(body))
+}

 var config map[string]interface{}

 if err = json.Unmarshal(body, &config); err != nil {
 	return "", err
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/sdk.go` around lines 467 - 481, The code calls client.Do(req) and
proceeds to read and unmarshal resp.Body without validating resp.StatusCode,
causing misleading JSON errors for non-2xx responses; after reading body (or
immediately after client.Do), check resp.StatusCode (e.g., if resp.StatusCode <
200 || resp.StatusCode >= 300) and return a clear error that includes
resp.StatusCode and a snippet of the response body. Update the block around
client.Do(req), resp, io.ReadAll(resp.Body) and json.Unmarshal so non-2xx
responses are detected and reported (referencing client.Do, resp, resp.Body,
io.ReadAll, and json.Unmarshal).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@sdk/sdk.go`:
- Around line 449-489: Add unit tests for getTokenEndpoint to verify issuer
normalization with both trailing and non-trailing slashes: create two
table-driven test cases supplying config objects whose
PlatformConfiguration["platform_issuer"] is "http://example.org" and
"http://example.org/" respectively, start an httptest.Server that serves a
/.well-known/openid-configuration JSON including
"token_endpoint":"http://example.org/token", and set c.httpClient (or inject the
test server URL via the client Transport) so getTokenEndpoint uses the test
server; assert the returned token endpoint matches the expected value and that
no error is returned to prevent regressions in issuer trimming.
- Around line 467-481: The code calls client.Do(req) and proceeds to read and
unmarshal resp.Body without validating resp.StatusCode, causing misleading JSON
errors for non-2xx responses; after reading body (or immediately after
client.Do), check resp.StatusCode (e.g., if resp.StatusCode < 200 ||
resp.StatusCode >= 300) and return a clear error that includes resp.StatusCode
and a snippet of the response body. Update the block around client.Do(req),
resp, io.ReadAll(resp.Body) and json.Unmarshal so non-2xx responses are detected
and reported (referencing client.Do, resp, resp.Body, io.ReadAll, and
json.Unmarshal).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9b7cefa-64c0-4299-94aa-cf0f120d7d1a

📥 Commits

Reviewing files that changed from the base of the PR and between 1623a3a and a39e1cb.

📒 Files selected for processing (1)
  • sdk/sdk.go

Replace manual string concatenation with url.JoinPath when building
the OIDC discovery URL from the issuer. This prevents double-slash
URLs when the issuer has a trailing slash.

Fixed in two locations:
- sdk/sdk.go getTokenEndpoint(): SDK's OIDC discovery for otdfctl/SDK
  consumers (uses SafeHTTPClient which does not follow redirects, so
  the double-slash 301 redirect caused "unexpected end of JSON input")
- service/internal/auth/discovery.go DiscoverOIDCConfiguration():
  platform's own OIDC discovery (uses http.DefaultClient which follows
  redirects, so it worked but made an unnecessary redirect round-trip)

IDPs like Authentik always include a trailing slash in their issuer URL
(e.g., https://idp.example/app/). This is RFC-compliant (RFC 8414).

Fixes opentdf#3260

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Dana Morris <dmorris@virtru.com>
@damorris25 damorris25 force-pushed the fix/oidc-discovery-trailing-slash branch from a39e1cb to 98a53e8 Compare April 6, 2026 14:05
@damorris25 damorris25 requested a review from a team as a code owner April 6, 2026 14:05
@damorris25 damorris25 added this pull request to the merge queue Apr 6, 2026
Merged via the queue into opentdf:main with commit 61f98c9 Apr 6, 2026
38 checks passed
@damorris25 damorris25 deleted the fix/oidc-discovery-trailing-slash branch April 6, 2026 14:53
marythought added a commit to opentdf/docs that referenced this pull request Apr 21, 2026
## Summary

Documents user-facing changes from the [SDK v0.16.0
release](https://github.com/opentdf/platform/releases/tag/sdk/v0.16.0):

- **KAS error classification (breaking change)**: New
`ErrKASRequestError` sentinel distinguishes misconfiguration from
tamper. Adds migration guide with error classification table and code
snippet for Go SDK consumers who check `ErrTampered`.
([opentdf/platform#3166](opentdf/platform#3166))
- **OIDC trailing-slash issuer fix**: Troubleshooting entry for
`unexpected end of JSON input` during OIDC discovery with IDPs like
Authentik that use trailing-slash issuer URLs.
([opentdf/platform#3261](opentdf/platform#3261))
- **GetObligationTrigger RPC**: New collapsible section with Go/JS
signatures, parameters, and examples — follows the existing
Add/List/Remove trigger pattern.
([opentdf/platform#3318](opentdf/platform#3318))
- **SDK version annotations**: New methods now include an *Available
since [SDK vX.Y.Z](link)* note after the signature block, linking to the
release

## Test plan

- [x] `npx docusaurus build` succeeds
- [x] `vale` passes with 0 errors/warnings/suggestions
- [ ] Verify Surge preview renders all three sections correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:sdk A software development kit, including library, for client applications and inter-service communicati size/xs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SDK: double-slash OIDC discovery URL when issuer has trailing slash

3 participants