feat(authserver): support additional token-request params (RFC 8707 resource indicators) - #6430
Conversation
Some authorization servers enforce RFC 8707 resource indicators on token requests as well as authorization requests: the code exchange and refresh are rejected with invalid_target unless the resource parameter is present in the POST form body (query-string placement is ignored). Nominal's MCP authorization server (api.gov.nominal.io) is a live example — with only additionalAuthorizationParams, the flow passes authorization and then fails at the code exchange. Add AdditionalTokenParams alongside AdditionalAuthorizationParams: - upstream.CommonOAuthConfig gains AdditionalTokenParams, applied in BaseOAuth2Provider.exchangeCodeForTokens and RefreshTokens via oauth2.SetAuthURLParam options (which land in the POST form body on Exchange). OIDC providers inherit both paths through the embedded base provider. - Reserved-parameter validation mirrors the authorization-side list with token-request semantics: grant_type, code, redirect_uri, client_id, client_secret, code_verifier, refresh_token, and scope are rejected. - CRD: additionalTokenParams on both oidcConfig and oauth2Config upstream provider types, plumbed through the operator run-config builders and validated at reconcile time (MCPExternalAuthConfig and VirtualMCPServer), matching the additionalAuthorizationParams treatment. - Regenerated deepcopy, CRD manifests, and CRD API docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Aron Gates <aron@muonspace.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6430 +/- ##
==========================================
- Coverage 78.98% 78.93% -0.05%
==========================================
Files 782 782
Lines 78065 78107 +42
==========================================
- Hits 61658 61656 -2
- Misses 16402 16446 +44
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
jhrozek
left a comment
There was a problem hiding this comment.
Sorry this took us so long to review, and thank you for the contribution — the RFC 8707 use case and the end-to-end token-form coverage are both very helpful.
blocker: Please reserve client_assertion and client_assertion_type in ReservedTokenParams. They are RFC 7523 client-authentication credentials; allowing them through additionalTokenParams can combine them with ToolHive's configured client_secret or HTTP Basic authentication, producing an invalid multi-method client-auth request. It also allows an assertion credential to live in CRD configuration instead of a secret-backed credential path.
suggestion: When resource is configured, validate it as an absolute URI with no fragment. RFC 8707 requires that shape, while the current name-only validation accepts an empty, relative, malformed, or fragment-bearing value that will fail later at the authorization server.
suggestion: Please add BuildAuthServerRunConfig tests that assert AdditionalTokenParams propagates for both OIDC and OAuth2 upstreams. The direct HTTP-form tests are good, but the new CRD-to-runtime mappings can otherwise regress silently; the adjacent AdditionalAuthorizationParams test cases are a natural template.
# Conflicts: # cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go # cmd/thv-operator/api/v1beta1/zz_generated.deepcopy.go # cmd/thv-operator/pkg/controllerutil/authserver.go # deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml # deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml # deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml # deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml # docs/operator/crd-api.md # pkg/authserver/upstream/oauth2_test.go
Merging main pushed validateUpstreamProvider to cyclomatic complexity 16 (limit 15): main added the caBundleRef check and this branch adds the additionalTokenParams one. Extract the per-type DCR/config checks into validateUpstreamProviderTypeConfig, mirroring the shape of validateUpstreamProviderCABundle. No behaviour change.
The merge with main carried the new field on the upstream provider configs but not the generated API contract, so docs-verify failed on a missing additional_token_params schema.
04c8c27 to
3586b2a
Compare
… resource indicator client_assertion and client_assertion_type are client-authentication credentials. Letting them through additionalTokenParams would combine an assertion with the configured client_secret or Basic credentials into an invalid multi-method client-auth request, and would keep a credential in plain CRD configuration rather than a secret-backed path. Both are now reserved. A resource entry is checked against the RFC 8707 shape, an absolute URI with no fragment, so a bad value fails at config time instead of at the authorization server on first login. Validation sits in ValidateTokenParams, which is the single choke point for the CRD and runtime paths. Adds BuildAuthServerRunConfig cases asserting AdditionalTokenParams propagates for both OIDC and OAuth2 upstreams.
|
Thanks, all three are in as of fa2ba08, on top of a merge with main. blocker,
Verified with One thing worth flagging separately, since it cost me a round trip here. |
jhrozek
left a comment
There was a problem hiding this comment.
Thanks for addressing the feedback — client_assertion/client_assertion_type are now reserved, the resource indicator is validated per RFC 8707, and the BuildAuthServerRunConfig tests cover both OIDC and OAuth2 propagation. LGTM.
Problem
Some authorization servers enforce RFC 8707 resource indicators on token requests, not just authorization requests: the code exchange and refresh are rejected with
invalid_targetunlessresourceis present in the POST form body (query-string placement is ignored).additionalAuthorizationParamsonly reaches the authorize URL, so against such an AS the embedded auth server passes authorization and then fails at the code exchange. Live example: Nominal's MCP authorization server (api.gov.nominal.io) — probing its token endpoint with a bogus code returns{"error":"invalid_target"}withoutresourcein the body, and proceeds to code validation (reauthorization_required) with it.Change
Adds
AdditionalTokenParamsalongsideAdditionalAuthorizationParams:upstream.CommonOAuthConfiggainsAdditionalTokenParams, applied inBaseOAuth2Provider.exchangeCodeForTokensandRefreshTokensviaoauth2.SetAuthURLParamoptions, which land in the POST form body onExchange. OIDC providers inherit both paths through the embedded base provider.grant_type,code,redirect_uri,client_id,client_secret,code_verifier,refresh_token, andscopeare rejected.additionalTokenParamson bothoidcConfigandoauth2Configupstream provider types, plumbed through the operator run-config builders and validated at reconcile time (MCPExternalAuthConfigandVirtualMCPServer), matching theadditionalAuthorizationParamstreatment.Testing
additionalTokenParamscases for both provider types.go build ./...,go test ./pkg/authserver/... ./cmd/thv-operator/api/v1beta1/ ./cmd/thv-operator/pkg/controllerutil/ ./cmd/thv-operator/controllers/all pass.🤖 Generated with Claude Code