Skip to content

[auth]: support oauth client_secret_basic / none / custom methods #720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jul 9, 2025

Conversation

ochafik
Copy link
Contributor

@ochafik ochafik commented Jul 1, 2025

This is an attempt to merge #531 and #552

Motivation and Context

This merges two sets of OAuth changes:

How Has This Been Tested?

WIP: testing in inspector

Breaking Changes

n/a

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

  • Main changes from McpError: MCP error -32601: Method not found #415:
    • Renamed callback to addClientAuthentication
    • Passed callback explicitly to refreshAuthorization & exchangeAuthorization, to avoid the awkwardness of them accepting a provider + other redundant parameters (codeVerifier, redirectUri)

jaredhanson and others added 19 commits May 21, 2025 19:14
… refreshAuthorization to maintain compatibility.
The applyBasicAuth function was incorrectly trying to set headers using
array notation on a Headers object. Fixed by using the proper Headers.set()
method instead of treating it as a plain object.

This ensures that HTTP Basic authentication works correctly when
client_secret_basic is the selected authentication method.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@jaredhanson
Copy link
Contributor

Thanks @ochafik ! This is looking pretty good. I'm experimenting with the branch right now, and have a suggested modification that I'll propose later tonight or tomorrow.

@jaredhanson
Copy link
Contributor

Hi @ochafik - I opened #723, with my proposed changes. Feel free to pull commits from that branch into this one, if you find them acceptable. Thanks for taking up these changes - really looking forward to them landing in a release. Let me know if I can do anything else.

Copy link
Contributor

@SightStudio SightStudio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know my feedback came a bit late, but it looks good to me.
I don't have any major comments to add.
Thank you for reviewing it positively!

@ochafik ochafik marked this pull request as ready for review July 7, 2025 12:32
Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only some nits, looks clean to me!

Comment on lines 11 to 13
type OAuthClientProvider,
} from "./auth.js";
import { OAuthMetadata } from 'src/shared/auth.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the project seems to use ../shared/auth.js as a pattern, i.e. relative imports (but I actually think this is better)

see discussion: https://anthropic.slack.com/archives/C0840HQ2Y2V/p1752075120212319

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone relative, thanks for the link

Comment on lines 89 to 92
* @param url - The token endpoint URL being called
* @param headers - The request headers (can be modified to add authentication)
* @param params - The request body parameters (can be modified to add credentials)
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ultra-nit: @params are in a different order than the args

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops, fixed

clientInformation: OAuthClientInformation,
supportedMethods: string[]
): string {
const hasClientSecret = !!clientInformation.client_secret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we don't seem to use !! notation anywhere, maybe prefer explicitness?

const hasClientSecret = clientInformation.client_secret !== undefined && clientInformation.client_secret !== '';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tightened this / only testing against undefined

function selectClientAuthMethod(
clientInformation: OAuthClientInformation,
supportedMethods: string[]
): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this maybe return an enum?

  enum ClientAuthMethod {
    BASIC = "client_secret_basic",
    POST = "client_secret_post",
    NONE = "none"
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines 175 to 188
if (method === "client_secret_basic") {
applyBasicAuth(client_id, client_secret, headers);
return;
}

if (method === "client_secret_post") {
applyPostAuth(client_id, client_secret, params);
return;
}

if (method === "none") {
applyPublicAuth(client_id, params);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could make this a switch with the enum potentially

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +649 to +656
if (addClientAuthentication) {
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
} else {
// Determine and apply client authentication method
const supportedMethods = metadata?.token_endpoint_auth_methods_supported ?? [];
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);

applyClientAuthentication(authMethod, clientInformation, headers, params);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

const body = request.body as URLSearchParams;
expect(body.get("client_id")).toBe("client123");
expect(body.get("client_secret")).toBe("secret123");
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have a test if it supports everything, that it chooses client_secret_basic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, added!

Comment on lines 1642 to 1643
expect(body.get("client_id")).toBeNull(); // should not be in body
expect(body.get("client_secret")).toBeNull(); // should not be in body
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: comments seem redundant

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Contributor Author

@ochafik ochafik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Felix!

Comment on lines 1642 to 1643
expect(body.get("client_id")).toBeNull(); // should not be in body
expect(body.get("client_secret")).toBeNull(); // should not be in body
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

const body = request.body as URLSearchParams;
expect(body.get("client_id")).toBe("client123");
expect(body.get("client_secret")).toBe("secret123");
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, added!

Comment on lines 89 to 92
* @param url - The token endpoint URL being called
* @param headers - The request headers (can be modified to add authentication)
* @param params - The request body parameters (can be modified to add credentials)
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops, fixed

function selectClientAuthMethod(
clientInformation: OAuthClientInformation,
supportedMethods: string[]
): string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

clientInformation: OAuthClientInformation,
supportedMethods: string[]
): string {
const hasClientSecret = !!clientInformation.client_secret;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tightened this / only testing against undefined

Comment on lines 175 to 188
if (method === "client_secret_basic") {
applyBasicAuth(client_id, client_secret, headers);
return;
}

if (method === "client_secret_post") {
applyPostAuth(client_id, client_secret, params);
return;
}

if (method === "none") {
applyPublicAuth(client_id, params);
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 11 to 13
type OAuthClientProvider,
} from "./auth.js";
import { OAuthMetadata } from 'src/shared/auth.js';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone relative, thanks for the link

@ochafik ochafik merged commit 031dfc2 into main Jul 9, 2025
5 checks passed
@ochafik ochafik deleted the ochafik/auth-merge-531-552 branch July 9, 2025 17:31
@ochafik
Copy link
Contributor Author

ochafik commented Jul 10, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants