Fix: Relax resource URI validation to accept base URL (#1517)
Co-authored-by: Jérémy Ballan <jballan@chapsvision.com> Co-authored-by: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com> Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
This commit is contained in:
parent
a87518cf44
commit
168c37179d
1 changed files with 122 additions and 16 deletions
|
|
@ -774,15 +774,19 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the resource URI in the metadata exactly matches the original request URL as required by the RFC.
|
||||
/// Per RFC: The resource value must be identical to the URL that the client used to make the request to the resource server.
|
||||
/// Verifies that the resource URI in the metadata matches the original request URL.
|
||||
/// Accepts either an exact match with the full request URL, or a match with the base URL
|
||||
/// (authority only, path discarded) as allowed by the MCP spec, which derives the authorization
|
||||
/// base URL by discarding the path component from the MCP server URL.
|
||||
/// </summary>
|
||||
/// <param name="protectedResourceMetadata">The metadata to verify.</param>
|
||||
/// <param name="resourceLocation">
|
||||
/// The original URL the client used to make the request to the resource server or the root Uri for the resource server
|
||||
/// if the metadata was automatically requested from the root well-known location.
|
||||
/// </param>
|
||||
/// <returns>True if the resource URI exactly matches the original request URL, otherwise false.</returns>
|
||||
/// <returns>
|
||||
/// True if the resource URI exactly matches the original request URL or its authority-level base URL, otherwise false.
|
||||
/// </returns>
|
||||
private static bool VerifyResourceMatch(ProtectedResourceMetadata protectedResourceMetadata, Uri resourceLocation)
|
||||
{
|
||||
if (protectedResourceMetadata.Resource is null)
|
||||
|
|
@ -790,14 +794,22 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
return false;
|
||||
}
|
||||
|
||||
// Per RFC: The resource value must be identical to the URL that the client used
|
||||
// to make the request to the resource server. Compare entire URIs, not just the host.
|
||||
|
||||
// Normalize the URIs to ensure consistent comparison
|
||||
string normalizedMetadataResource = NormalizeUri(protectedResourceMetadata.Resource);
|
||||
string normalizedResourceLocation = NormalizeUri(resourceLocation);
|
||||
|
||||
return string.Equals(normalizedMetadataResource, normalizedResourceLocation, StringComparison.OrdinalIgnoreCase);
|
||||
// Accept exact match with the full MCP endpoint URI
|
||||
if (string.Equals(normalizedMetadataResource, normalizedResourceLocation, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Per the MCP spec's "Canonical Server URI" section, both the path-specific URI (e.g. https://mcp.example.com/mcp)
|
||||
// and the authority-only URI (e.g. https://mcp.example.com) are valid canonical URIs for identifying an MCP server.
|
||||
// Accept a match with the base URL (authority only, path discarded) to support servers that use the less specific form.
|
||||
|
||||
string normalizedBaseUrl = NormalizeUri(new Uri(resourceLocation.GetLeftPart(UriPartial.Authority)));
|
||||
return string.Equals(normalizedMetadataResource, normalizedBaseUrl, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -916,7 +928,8 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
// https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements
|
||||
metadata.WwwAuthenticateScope = wwwAuthenticateScope;
|
||||
|
||||
// Per RFC: The resource value must be identical to the URL that the client used to make the request to the resource server
|
||||
// Validate that the resource URI in metadata corresponds to the server we're connecting to.
|
||||
// VerifyResourceMatch accepts both an exact URI match and an authority-level (base URL) match per the MCP spec.
|
||||
LogValidatingResourceMetadata(resourceUri);
|
||||
|
||||
if (!isLegacyFallback && !VerifyResourceMatch(metadata, resourceUri))
|
||||
|
|
|
|||
Loading…
Reference in a new issue