Bring up to 0.1.13 conformance (#1254)
This commit is contained in:
parent
9c1538fd98
commit
27b5eb8031
1 changed files with 1249 additions and 78 deletions
|
|
@ -44,6 +44,7 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
|
||||
private string? _clientId;
|
||||
private string? _clientSecret;
|
||||
private string? _tokenEndpointAuthMethod;
|
||||
private ITokenCache _tokenCache;
|
||||
private AuthorizationServerMetadata? _authServerMetadata;
|
||||
|
||||
|
|
@ -293,6 +294,9 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
}
|
||||
}
|
||||
|
||||
// Determine the token endpoint auth method from server metadata if not already set by DCR.
|
||||
_tokenEndpointAuthMethod ??= authServerMetadata.TokenEndpointAuthMethodsSupported?.FirstOrDefault();
|
||||
|
||||
// Store auth server metadata for future refresh operations
|
||||
_authServerMetadata = authServerMetadata;
|
||||
|
||||
|
|
@ -385,20 +389,15 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
|
||||
private async Task<string?> RefreshTokensAsync(string refreshToken, Uri resourceUri, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)
|
||||
{
|
||||
var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
Dictionary<string, string> formFields = new()
|
||||
{
|
||||
["grant_type"] = "refresh_token",
|
||||
["refresh_token"] = refreshToken,
|
||||
["client_id"] = GetClientIdOrThrow(),
|
||||
["client_secret"] = _clientSecret ?? string.Empty,
|
||||
["resource"] = resourceUri.ToString(),
|
||||
});
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.TokenEndpoint)
|
||||
{
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
using var request = CreateTokenRequest(authServerMetadata.TokenEndpoint, formFields);
|
||||
|
||||
using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!httpResponse.IsSuccessStatusCode)
|
||||
|
|
@ -482,22 +481,17 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
{
|
||||
var resourceUri = GetRequiredResourceUri(protectedResourceMetadata);
|
||||
|
||||
var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
Dictionary<string, string> formFields = new()
|
||||
{
|
||||
["grant_type"] = "authorization_code",
|
||||
["code"] = authorizationCode,
|
||||
["redirect_uri"] = _redirectUri.ToString(),
|
||||
["client_id"] = GetClientIdOrThrow(),
|
||||
["code_verifier"] = codeVerifier,
|
||||
["client_secret"] = _clientSecret ?? string.Empty,
|
||||
["resource"] = resourceUri.ToString(),
|
||||
});
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.TokenEndpoint)
|
||||
{
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
using var request = CreateTokenRequest(authServerMetadata.TokenEndpoint, formFields);
|
||||
|
||||
using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
|
@ -506,6 +500,38 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
return tokens.AccessToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an HTTP request to the token endpoint, applying the appropriate authentication
|
||||
/// method based on <see cref="_tokenEndpointAuthMethod"/>.
|
||||
/// </summary>
|
||||
private HttpRequestMessage CreateTokenRequest(Uri tokenEndpoint, Dictionary<string, string> formFields)
|
||||
{
|
||||
HttpRequestMessage request = new(HttpMethod.Post, tokenEndpoint);
|
||||
|
||||
var clientId = GetClientIdOrThrow();
|
||||
if (string.Equals(_tokenEndpointAuthMethod, "client_secret_basic", StringComparison.Ordinal))
|
||||
{
|
||||
// Per RFC 6749 §2.3.1: send client_id:client_secret as HTTP Basic auth.
|
||||
request.Headers.Authorization = new(
|
||||
"Basic",
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Uri.EscapeDataString(clientId)}:{Uri.EscapeDataString(_clientSecret ?? string.Empty)}")));
|
||||
}
|
||||
else if (string.Equals(_tokenEndpointAuthMethod, "none", StringComparison.Ordinal))
|
||||
{
|
||||
// Public client: include client_id in the body but no secret.
|
||||
formFields["client_id"] = clientId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to client_secret_post: include credentials in the body.
|
||||
formFields["client_id"] = clientId;
|
||||
formFields["client_secret"] = _clientSecret ?? string.Empty;
|
||||
}
|
||||
|
||||
request.Content = new FormUrlEncodedContent(formFields);
|
||||
return request;
|
||||
}
|
||||
|
||||
private async Task<TokenContainer> HandleSuccessfulTokenResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -620,6 +646,11 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
_clientSecret = registrationResponse.ClientSecret;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(registrationResponse.TokenEndpointAuthMethod))
|
||||
{
|
||||
_tokenEndpointAuthMethod = registrationResponse.TokenEndpointAuthMethod;
|
||||
}
|
||||
|
||||
LogDynamicClientRegistrationSuccessful(_clientId!);
|
||||
|
||||
if (_dcrResponseDelegate is not null)
|
||||
|
|
|
|||
Loading…
Reference in a new issue