Fix DCR failure for authorization servers with non-root issuer paths#5357
Conversation
resolveDCRCredentials constructed a single OIDC issuer-suffix URL
({issuer}/.well-known/openid-configuration) and passed it to the DCR
resolver, which fetched exactly that URL via
FetchAuthorizationServerMetadataFromURL. Authorization servers that
serve their RFC 8414 metadata exclusively at the path-insertion URL
(scheme://host/.well-known/oauth-authorization-server/{path}) — such as
Gleean's AS with issuer https://example.com/oauth — received a 404 or
HTML response and the CLI failed with "unexpected content-type text/html"
before opening the browser.
Replace the single-URL construction with oauthproto.FetchAuthorizationServerMetadata,
which tries the three well-known URL forms in priority order (RFC 8414
path-insertion, OIDC issuer-suffix, bare RFC 8414), restoring the
fallback behaviour that existed in v0.27.x via
discoverOIDCEndpointsWithClientAndValidation. The fetched
code_challenge_methods_supported is forwarded to the resolver through a
new dcr.Request.CodeChallengeMethodsSupported field, so the S256 PKCE
gate fires without a second discovery round-trip inside the resolver.
Regression test added: TestHandleDynamicRegistration_NonRootIssuerRFC8414PathInsertion
mounts metadata only at /.well-known/oauth-authorization-server/oauth
and verifies DCR succeeds end-to-end.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Three gaps in the test suite left by the #5356 fix: 1. pkg/auth/dcr: TestResolveDCRCredentials_CodeChallengeMethodsSupportedFieldEnablesPublicClient pins the new Request.CodeChallengeMethodsSupported field on the RegistrationEndpoint-direct branch. Without a DiscoveryURL the S256 gate has no metadata to read from; the field is the only input. Four cases: S256 allows public client, plain rejects, absent rejects, absent is fine for confidential clients. 2. pkg/auth/discovery: TestHandleDynamicRegistration_PreDiscoveredPathNonRootIssuer covers the pre-discovered path (config.RegistrationEndpoint/AuthorizeURL/ TokenURL all pre-set) with a non-root issuer. getDiscoveryDocument short-circuits on this path and returns a synthesised document with empty code_challenge_methods_supported; the multi-URL re-fetch in resolveDCRCredentials must still reach the path-insertion URL correctly. 3. pkg/auth/discovery: TestHandleDynamicRegistration_SynthesisesEndpointWhenMetadataOmitsIt verifies that when FetchAuthorizationServerMetadata returns ErrRegistrationEndpointMissing (valid metadata, no registration_endpoint), resolveDCRCredentials synthesises {issuer}/register and routes the registration there — the nanobot/Hydra convention also applied by the resolver's DiscoveryURL branch. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5357 +/- ##
==========================================
+ Coverage 68.36% 68.40% +0.03%
==========================================
Files 622 624 +2
Lines 63371 63459 +88
==========================================
+ Hits 43326 43410 +84
Misses 16809 16809
- Partials 3236 3240 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
tgrunnagle
left a comment
There was a problem hiding this comment.
Fix correctly addresses #5356 — the multi-URL fallback restores v0.27.x behavior and the regression test mounts metadata only at the RFC 8414 path-insertion URL, which is exactly the failure scenario from the issue. Doc comments are updated cleanly and the deferred architectural follow-up (threading capability fields through AuthServerInfo) is appropriately scoped out. One non-blocking note inline about synthesis duplication.
Summary
Authorization servers whose issuer has a non-root path (e.g.
https://example.com/oauth) may serve their RFC 8414 metadata exclusively at the path-insertion URL (scheme://host/.well-known/oauth-authorization-server/path), not at the OIDC issuer-suffix URL ({issuer}/.well-known/openid-configuration). After upgrading to v0.28.0,thv runwould fail for these servers before opening the browser — the workload never entered running state.resolveDCRCredentialsto getcode_challenge_methods_supportedfor the S256 PKCE gate. It constructed a single OIDC issuer-suffix URL for that fetch and passed it toFetchAuthorizationServerMetadataFromURL, which fetches exactly one URL with no fallback. Servers that only serve the RFC 8414 path-insertion URL returned HTML or a 404, producing theunexpected content-type "text/html"error from the issue.discoverOIDCEndpointsWithClientAndValidation) tried both URL forms and fell back gracefully; that fallback was lost in the migration.oauthproto.FetchAuthorizationServerMetadata, which tries all three well-known URL forms in priority order (RFC 8414 path-insertion → OIDC issuer-suffix → bare RFC 8414). The fetchedcode_challenge_methods_supportedis forwarded to the resolver via a newdcr.Request.CodeChallengeMethodsSupportedfield so the S256 PKCE gate fires without a second round-trip inside the resolver.Fixes #5356.
Type of change
Test plan
task test)task lint-fix)New and updated tests:
TestHandleDynamicRegistration_NonRootIssuerRFC8414PathInsertion— regression test: mounts metadata only at/.well-known/oauth-authorization-server/oauth, verifies DCR succeeds end-to-end (would have failed before the fix).TestHandleDynamicRegistration_PreDiscoveredPathNonRootIssuer— same scenario on the pre-discovered path (endpoints already inOAuthFlowConfig), which is the more common production case.TestHandleDynamicRegistration_SynthesisesEndpointWhenMetadataOmitsIt— verifies theErrRegistrationEndpointMissingsynthesis branch inresolveDCRCredentialsroutes to{issuer}/register.TestResolveDCRCredentials_CodeChallengeMethodsSupportedFieldEnablesPublicClient— pins the newdcr.Request.CodeChallengeMethodsSupportedfield: S256 present allows public client,plain-only or absent rejects it, absent is fine for confidential clients.API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.Changes
pkg/auth/dcr/request.goCodeChallengeMethodsSupported []stringtoRequest; active on theRegistrationEndpointbranch so callers can supply already-discovered capability fields.pkg/auth/dcr/resolver.goreq.CodeChallengeMethodsSupportedintodcrEndpointson theRegistrationEndpointbranch so the S256 gate fires from the caller-supplied field.pkg/auth/discovery/discovery.goresolveDCRCredentialswithoauthproto.FetchAuthorizationServerMetadata(multi-URL fallback); forwardCodeChallengeMethodsSupportedto the resolver. Update function doc.pkg/auth/discovery/dcr_resolver_test.gopkg/auth/dcr/resolver_test.goCodeChallengeMethodsSupportedfield onRegistrationEndpointbranch.Does this introduce a user-facing change?
DCR now succeeds for authorization servers with non-root issuer paths (e.g.
https://example.com/oauth) that serve RFC 8414 metadata at the path-insertion URL. These servers were broken in v0.28.0; this restores v0.27.x behaviour.Special notes for reviewers
The architectural root cause (#5250 doc comment, "known limitation of the option (b) migration") is that
code_challenge_methods_supportedis not threaded throughAuthServerInfoto the CLI call site. That follow-up (threading the field throughValidateAndDiscoverAuthServerand all its callers) would eliminate the re-fetch entirely on the pre-discovered path and remains open. This PR takes the minimal targeted fix: use the multi-URL fallback for the re-fetch and forward the result via the new field, without touchingAuthServerInfoor its callers.Generated with Claude Code