Conversation
…o secret) POST /oauth/token was rejecting every authorization_code grant from public clients (token_endpoint_auth_method=none) with invalid_client. The handler only routed refresh_token through the public-client-capable authenticator; authorization_code fell through to authenticateClient which demands a client_secret. OAuth 2.1 §4.1.3: PKCE code_verifier + client_id binds the code to the client; no client_secret is needed for public clients. Claude Code and every other dynamic-reg PKCE client was broken by this. Fix: - Rename authenticateClientForRefresh → authenticateClientPublicOrConfidential (the logic was already grant-agnostic; the name was misleading) - Use it for authorization_code grants too - client_credentials stays confidential-only (cannot be a public grant) - Add test covering public client by client_id alone Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
View your CI Pipeline Execution ↗ for commit f149a1b
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Code Review
This pull request updates the OAuth token endpoint to support public clients for the authorization_code grant type, aligning with OAuth 2.1 specifications. The function authenticateClientForRefresh has been renamed to authenticateClientPublicOrConfidential and is now utilized for both authorization_code and refresh_token grants. This allows public clients (where token_endpoint_auth_method is 'none') to authenticate using only their client_id. A corresponding test case was added to ensure correct behavior for public clients during the authorization code flow. I have no feedback to provide.
Summary
POST /oauth/tokenwithgrant_type=authorization_codefrom a public client (dyn-reg,token_endpoint_auth_method=none) was being rejected withinvalid_client— the handler only routedrefresh_tokenthrough the public-client-capable authenticatorcode_verifier+client_idbinds the code to the client; noclient_secretis requiredauthenticateClientForRefresh→authenticateClientPublicOrConfidential(logic was already grant-agnostic; name was misleading) and use it forauthorization_codetooclient_credentialsstays confidential-only by definitionHit in production when Claude Code's dyn-reg flow completed the browser step and the callback exchange blew up silently. Every real MCP 2025-06-18 client that uses dyn-reg + PKCE was broken by this.
Test plan
token_endpoint_auth_method=nonegets tokens from/oauth/tokenwithclient_idalone (noclient_secret, no Basic header);passwordHasher.verifyPasswordnever calledclient_secretpath + Basic auth path)🤖 Generated with Claude Code