Conversation
Documentation CheckUpdates Needed
RationaleThis PR adds RFC 6749 §2.3.1
The documentation currently only shows form-based authentication ( Automated review via Coder Tasks |
|
@coder-tasks Thanks! Updated docs in Re: breaking change concern — Dynamic Client Registration already defaulted |
Change-Id: I8e0ff01588c2208a506ea278d1b1d6d1a7f4fd4c Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: If64da5088deff45313105e21f5ef478274bb5e6c Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I2f9cd1151557827a221d2e960169faa962350b18 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I72c0e6fe3213b46e7b7fb48427e8542529d76ef5 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Adds RFC 6749 §2.3.1
client_secret_basicsupport to Coder's OAuth2 provider.client_idfrom HTTP Basic auth for OAuth2 app lookup middleware./oauth2/tokensto authenticate clients viaAuthorization: Basic.invalid_request.client_secret_basic+client_secret_postin discovery metadata.WWW-Authenticateheader for401 invalid_clientresponses.📋 Implementation Plan
Plan: Add OAuth2
client_secret_basic(HTTP Basic auth) supportContext / why
codex mcp addis failing during the OAuth2 token exchange with:invalid_request: Missing client_id parameterCoder’s OAuth2 provider currently identifies the client (
client_id) and authenticates it (client_secret) only via query/form parameters, but RFC 6749 §2.3.1 requires supporting HTTP Basic authentication for confidential clients. This is especially important because Dynamic Client Registration (RFC 7591) defaultstoken_endpoint_auth_methodtoclient_secret_basic.Evidence (what we verified)
coderd/httpmw/oauth2.go:extractOAuth2ProviderAppBasechecksclient_idonly in query + form body and returnsMissing client_id parameterotherwise.coderd/oauth2provider/tokens.go:extractTokenRequestrequiresclient_idandclient_secretas form fields forauthorization_codegrant.coderd/oauth2provider/metadata.go: discovery metadata advertises onlyclient_secret_posttoday.curlshows/oauth2/tokenscorrectly parses form bodies (returnsunsupported_grant_typewhen onlyclient_idis supplied), so the failure is specifically “client_id not found where Coder looks for it”.Goals
/oauth2/tokens(and other OAuth2 endpoints that expect client auth).client_secret_postbehavior working.client_secret_basicandclient_secret_post.invalid_clientwith HTTP 401 should includeWWW-Authenticate.Non-goals
client_idformat (it remains the app UUID).token_endpoint_auth_method=none) behavior beyond what’s needed for Basic auth.Implementation plan
1) Parse
client_idfrom HTTP Basic in the OAuth2 app lookup middlewareFile:
coderd/httpmw/oauth2.goTarget:
extractOAuth2ProviderAppBaseChange: when
client_idis not present in URL param, query string, or form body, fall back to HTTP Basic username.2) Support
client_secret_basicduring token exchangeFile:
coderd/oauth2provider/tokens.goTargets:
extractTokenRequest, and its callsite inTokens().Change: after parsing the form, merge credentials from form + Basic auth:
basicUser/basicPass := r.BasicAuth()formClientID := vals.Get("client_id"),formClientSecret := vals.Get("client_secret")invalid_request(defensive + avoids ambiguous auth).grant_type, but validate the mergedclient_id/client_secretinstead of requiring they exist in the form.Sketch of the intended logic
3) Update discovery metadata to advertise Basic auth support
File:
coderd/oauth2provider/metadata.goChange:
TokenEndpointAuthMethodsSupported: [client_secret_post]TokenEndpointAuthMethodsSupported: [client_secret_basic, client_secret_post]4) Add RFC-required
WWW-Authenticateheader forinvalid_clientFile:
coderd/httpapi/httpapi.goTarget:
WriteOAuth2ErrorChange: When writing an OAuth2 error with
status==401anderror==invalid_client, set:This aligns with RFC 6749 §5.2 (“MUST include WWW-Authenticate when using 401”).
5) Tests
Add/extend tests to prevent regressions:
Token endpoint accepts Basic auth
/oauth2/tokenswhile:req.SetBasicAuth(clientID, clientSecret)client_id+client_secretfrom the form bodyInvalid secret via Basic returns
invalid_clientand includesWWW-Authenticateinvalid_client, andWWW-Authenticate: Basic realm="coder".Metadata advertises both auth methods
coderd/oauth2provider/metadata_test.goto assertTokenEndpointAuthMethodsSupportedcontains bothclient_secret_basicandclient_secret_post.Acceptance criteria
codex mcp add ...succeeds end-to-end against a Coder instance with OAuth2 provider enabled./oauth2/tokensworks for both:client_secret_post(existing behavior)client_secret_basic(new behavior).well-known/oauth-authorization-serverincludestoken_endpoint_auth_methods_supportedwithclient_secret_basic.invalid_clientresponses return 401 + aWWW-Authenticateheader.Generated with
mux• Model:openai:gpt-5.2• Thinking:xhigh