Skip to content

Conversation

@challenger71498
Copy link

@challenger71498 challenger71498 commented Jan 11, 2026

Summary

This PR implements OAuth client authentication by supporting HTTP Basic authentication as defined in RFC 6749 Section 2.3.1., especially when client_id is provided only from the header.

The key changes are:

  • Extract client credentials from Basic auth header first, before falling back to form data
  • Validate that the authentication method used matches what the client registered
  • Make client_id optional in token request body when using Basic auth (as the spec allows)

Previously, the code would first read client_id from the form body and then validate against the auth method. This caused issues when clients used Basic auth without including client_id in the request body, which is valid per the OAuth spec.

Motivation and Context

  • RFC 6749 Section 2.3.1 specifies that clients using client_secret_basic should send credentials via the Authorization header, and the client_id in the request body is optional in this case.
  • The previous implementation required client_id in the body even when using Basic auth, which was non-compliant with the spec.

How Has This Been Tested?

Unit Test

  • Added test for Basic auth working without client_id in request body
  • Added test for Basic auth taking precedence over form body client_id
  • Added test for refresh token flow without client_id in body
  • Updated existing tests to reflect new error messages when auth method mismatches

E2E Test

  • Tested with npx @modelcontextprotocol/inspector + OAuth provider mockup, and worked as expected.
Screenshots image image

Breaking Changes

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 no docs need to be updated

Additional context


return client

async def _get_credentials(self, request: Request) -> ClientCredentials:
Copy link
Author

Choose a reason for hiding this comment

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

Note: the core extraction logic of client_id and client_secret is as same as before.


@pytest.mark.anyio
async def test_wrong_auth_method_without_valid_credentials_fails(
async def test_wrong_auth_method_fails(
Copy link
Author

@challenger71498 challenger71498 Jan 11, 2026

Choose a reason for hiding this comment

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

Note: test fails as same as before, only exception message has been changed, due to the auth method mismatch.

# RFC 6749: authentication failures return "invalid_client"
assert error_response["error"] == "invalid_client"
assert "Missing or invalid Basic authentication" in error_response["error_description"]
assert "Expected client_secret_basic authentication method" in error_response["error_description"]
Copy link
Author

@challenger71498 challenger71498 Jan 11, 2026

Choose a reason for hiding this comment

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

Note: test fails as same as before, only exception message has been changed, due to the auth method mismatch.


@pytest.mark.anyio
async def test_basic_auth_client_id_mismatch_fails(
async def test_basic_auth_takes_precedence(
Copy link
Author

@challenger71498 challenger71498 Jan 11, 2026

Choose a reason for hiding this comment

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

Note: the behaviour of the test has been changed. After the changes, basic auth takes precedence over form data, so even if there is a mismatch, the middleware retrieves the client_id from the header.

This behaviour is intended due to RFC 6749 Section 2.3.1:

Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme (or other password-based HTTP authentication schemes).

If this is inappropriate, alternatives are:

  • behave AS-IS (throw mismatch exception)
  • disallow dup auth method
    • throw exception which indicates that more than one authentication method was used.

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.

1 participant