Skip to content

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

@damorris25

Description

@damorris25

Summary

The Go SDK constructs a double-slash OIDC discovery URL when the platform issuer has a trailing slash. Combined with SafeHTTPClient's no-redirect policy, this causes all SDK/otdfctl operations to fail with unexpected end of JSON input against any IDP whose issuer ends with /.

This affects Authentik (and potentially other IDPs). Trailing slashes in issuer URLs are permitted by RFC 8414.

Root Cause

sdk/sdk.go:456 concatenates without normalizing:

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

When issuerURL = https://idp.example/app/ (trailing slash from IDP), this produces:

https://idp.example/app//.well-known/openid-configuration

sdk/httputil/http.go SafeHTTPClient is configured to never follow redirects (http.ErrUseLastResponse). The double-slash URL returns a 301 redirect with an empty bodyjson.Unmarshal("")unexpected end of JSON input.

Why the issuer has a trailing slash

The platform's auth init (service/internal/auth/authn.go:128-129) replaces the configured issuer with the issuer field from the OIDC discovery response. Authentik's discovery response always includes a trailing slash (hardcoded in Django's URL routing). This issuer is then exposed in /.well-known/opentdf-configuration, which the SDK reads.

Reproduction

# Deploy platform with Authentik as IDP, then:
curl -sk https://platform:9000/.well-known/opentdf-configuration | jq .configuration.platform_issuer
# → "https://idp:9443/application/o/app/"   ← trailing slash

otdfctl policy attributes list --host https://platform:9000 --tls-no-verify --with-access-token "$TOKEN"
# → "Unexpected error: unexpected end of JSON input"

# Same API works via curl:
curl -sk -X POST https://platform:9000/policy.attributes.AttributesService/ListAttributes \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
# → valid JSON

Suggested Fix

Option A — Normalize issuer URL (minimal, recommended)

// sdk/sdk.go:456
oidcConfigURL := strings.TrimRight(issuerURL, "/") + "/.well-known/openid-configuration"

Option B — Follow redirects for discovery endpoints (defense in depth)

// sdk/httputil/http.go - SafeHTTPClient
CheckRedirect: func(req *http.Request, via []*http.Request) error {
    if strings.Contains(req.URL.Path, ".well-known") || strings.Contains(req.URL.Path, "/jwks") {
        return nil
    }
    return http.ErrUseLastResponse
},

Both can be applied together — A prevents the bad URL, B adds resilience against other IDP URL normalization differences.

Workaround

Placing an nginx reverse proxy with merge_slashes on in front of the IDP normalizes the double-slash before it reaches the IDP. With this in place, otdfctl --with-access-token works. This is infrastructure-level only — the SDK still constructs an invalid URL.

Environment

Component Version
otdfctl v0.26.2
OpenTDF Go SDK v0.11.0
OpenTDF Platform v2.7.8
Authentik 2026.2.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions