Skip to content

Commit d3591d6

Browse files
committed
Update AuthorizationHelpers.cs
1 parent c6b1e3a commit d3591d6

1 file changed

Lines changed: 18 additions & 60 deletions

File tree

src/ModelContextProtocol.Core/Authentication/AuthorizationHelpers.cs

Lines changed: 18 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,9 @@ namespace ModelContextProtocol.Authentication;
88
/// Provides utility methods for handling authentication in MCP clients.
99
/// </summary>
1010
public class AuthorizationHelpers
11-
{
12-
private readonly HttpClient _httpClient;
11+
{ private readonly HttpClient _httpClient;
1312
private readonly ILogger _logger;
1413
private static readonly Lazy<HttpClient> _defaultHttpClient = new(() => new HttpClient());
15-
16-
/// <summary>
17-
/// The well-known path prefix for resource metadata.
18-
/// </summary>
19-
private const string WellKnownPathPrefix = "/.well-known/";
2014

2115
/// <summary>
2216
/// Initializes a new instance of the <see cref="AuthorizationHelpers"/> class.
@@ -57,11 +51,12 @@ public AuthorizationHelpers(HttpClient? httpClient = null, ILogger? logger = nul
5751
}
5852

5953
/// <summary>
60-
/// Verifies that the resource URI in the metadata exactly matches the server URL as required by the RFC.
54+
/// Verifies that the resource URI in the metadata exactly matches the original request URL as required by the RFC.
55+
/// Per RFC: The resource value must be identical to the URL that the client used to make the request to the resource server.
6156
/// </summary>
6257
/// <param name="protectedResourceMetadata">The metadata to verify.</param>
63-
/// <param name="resourceLocation">The server URL to compare against.</param>
64-
/// <returns>True if the resource URI exactly matches the server, otherwise false.</returns>
58+
/// <param name="resourceLocation">The original URL the client used to make the request to the resource server.</param>
59+
/// <returns>True if the resource URI exactly matches the original request URL, otherwise false.</returns>
6560
private static bool VerifyResourceMatch(ProtectedResourceMetadata protectedResourceMetadata, Uri resourceLocation)
6661
{
6762
if (protectedResourceMetadata.Resource == null || resourceLocation == null)
@@ -71,11 +66,11 @@ private static bool VerifyResourceMatch(ProtectedResourceMetadata protectedResou
7166

7267
// Per RFC: The resource value must be identical to the URL that the client used
7368
// to make the request to the resource server. Compare entire URIs, not just the host.
74-
69+
7570
// Normalize the URIs to ensure consistent comparison
7671
string normalizedMetadataResource = NormalizeUri(protectedResourceMetadata.Resource);
7772
string normalizedResourceLocation = NormalizeUri(resourceLocation);
78-
73+
7974
return string.Equals(normalizedMetadataResource, normalizedResourceLocation, StringComparison.OrdinalIgnoreCase);
8075
}
8176

@@ -100,44 +95,7 @@ private static string NormalizeUri(Uri uri)
10095
builder.Path = builder.Path.TrimEnd('/');
10196
}
10297

103-
return builder.Uri.ToString();
104-
}
105-
106-
/// <summary>
107-
/// Extracts the base resource URI from a well-known path URL.
108-
/// </summary>
109-
/// <param name="metadataUri">The metadata URI containing a well-known path.</param>
110-
/// <returns>The base URI without the well-known path component.</returns>
111-
/// <exception cref="InvalidOperationException">Thrown when the URI does not contain a valid well-known path.</exception>
112-
private Uri ExtractBaseResourceUri(Uri metadataUri)
113-
{
114-
// Check for well-known path
115-
int wellKnownIndex = metadataUri.AbsolutePath.IndexOf(WellKnownPathPrefix, StringComparison.OrdinalIgnoreCase);
116-
117-
// Validate the URL contains a valid well-known path
118-
if (wellKnownIndex < 0)
119-
{
120-
throw new InvalidOperationException(
121-
$"Resource metadata URL '{metadataUri}' does not contain a valid well-known path format (/.well-known/)");
122-
}
123-
124-
// Create URI with just the base part
125-
var baseUriBuilder = new UriBuilder(metadataUri)
126-
{
127-
Path = wellKnownIndex > 0 ? metadataUri.AbsolutePath.Substring(0, wellKnownIndex) : "/",
128-
Fragment = string.Empty,
129-
Query = string.Empty,
130-
Port = -1 // Remove port
131-
};
132-
133-
// Ensure path ends with a slash
134-
if (!baseUriBuilder.Path.EndsWith("/"))
135-
{
136-
baseUriBuilder.Path += "/";
137-
}
138-
139-
return baseUriBuilder.Uri;
140-
}
98+
return builder.Uri.ToString(); }
14199

142100
/// <summary>
143101
/// Responds to a 401 challenge by parsing the WWW-Authenticate header, fetching the resource metadata,
@@ -185,22 +143,20 @@ internal async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
185143
throw new InvalidOperationException("The WWW-Authenticate header does not contain a resource_metadata parameter");
186144
}
187145

188-
Uri metadataUri = new(resourceMetadataUrl);
189-
190-
var metadata = await FetchProtectedResourceMetadataAsync(metadataUri, cancellationToken).ConfigureAwait(false);
146+
Uri metadataUri = new(resourceMetadataUrl); var metadata = await FetchProtectedResourceMetadataAsync(metadataUri, cancellationToken).ConfigureAwait(false);
191147
if (metadata == null)
192148
{
193149
throw new InvalidOperationException($"Failed to fetch resource metadata from {resourceMetadataUrl}");
194150
}
195151

196-
// Extract the base URI from the metadata URL
197-
Uri urlToValidate = ExtractBaseResourceUri(metadataUri);
198-
_logger.LogDebug($"Validating resource metadata against base URL: {urlToValidate}");
152+
// Per RFC: The resource value must be identical to the URL that the client used
153+
// to make the request to the resource server
154+
_logger.LogDebug($"Validating resource metadata against original server URL: {serverUrl}");
199155

200-
if (!VerifyResourceMatch(metadata, urlToValidate))
156+
if (!VerifyResourceMatch(metadata, serverUrl))
201157
{
202158
throw new InvalidOperationException(
203-
$"Resource URI in metadata ({metadata.Resource}) does not match the expected URI ({urlToValidate})");
159+
$"Resource URI in metadata ({metadata.Resource}) does not match the expected URI ({serverUrl})");
204160
}
205161

206162
return metadata;
@@ -245,7 +201,9 @@ internal async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
245201
}
246202

247203
return null;
248-
} /// <summary>
204+
}
205+
206+
/// <summary>
249207
/// Handles a 401 Unauthorized response and returns all available authorization servers.
250208
/// This is the primary method for OAuth discovery - use this when you want full control
251209
/// over authorization server selection.
@@ -261,7 +219,7 @@ public async Task<IReadOnlyList<Uri>> GetAvailableAuthorizationServersAsync(
261219
CancellationToken cancellationToken = default)
262220
{
263221
if (response == null) throw new ArgumentNullException(nameof(response));
264-
222+
265223
try
266224
{
267225
// Extract resource metadata behind the scenes

0 commit comments

Comments
 (0)