Skip to content

Commit b8ae4fe

Browse files
authored
Add auth regression test for missing resource in PRM (#1265)
1 parent 53e9ed8 commit b8ae4fe

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.AspNetCore.Http;
55
using Microsoft.AspNetCore.WebUtilities;
66
using Microsoft.Extensions.DependencyInjection;
7+
using ModelContextProtocol;
78
using ModelContextProtocol.AspNetCore.Authentication;
89
using ModelContextProtocol.Authentication;
910
using ModelContextProtocol.Client;
@@ -528,6 +529,47 @@ await Assert.ThrowsAsync<McpException>(() => McpClient.CreateAsync(
528529
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
529530
}
530531

532+
[Fact]
533+
public async Task CannotAuthenticate_WhenProtectedResourceMetadataMissingResource()
534+
{
535+
TestOAuthServer.RequireResource = false;
536+
537+
Builder.Services.Configure<McpAuthenticationOptions>(McpAuthenticationDefaults.AuthenticationScheme, options =>
538+
{
539+
options.Events.OnResourceMetadataRequest = async context =>
540+
{
541+
context.HandleResponse();
542+
543+
var metadata = new ProtectedResourceMetadata
544+
{
545+
AuthorizationServers = { new Uri(OAuthServerUrl) },
546+
ScopesSupported = ["mcp:tools"],
547+
};
548+
549+
await Results.Json(metadata, McpJsonUtilities.DefaultOptions).ExecuteAsync(context.HttpContext);
550+
};
551+
});
552+
553+
await using var app = await StartMcpServerAsync();
554+
555+
await using var transport = new HttpClientTransport(new()
556+
{
557+
Endpoint = new(McpServerUrl),
558+
OAuth = new()
559+
{
560+
ClientId = "demo-client",
561+
ClientSecret = "demo-secret",
562+
RedirectUri = new Uri("http://localhost:1179/callback"),
563+
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
564+
},
565+
}, HttpClient, LoggerFactory);
566+
567+
var ex = await Assert.ThrowsAsync<McpException>(() => McpClient.CreateAsync(
568+
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
569+
570+
Assert.Contains("Resource URI in metadata", ex.Message);
571+
}
572+
531573
[Fact]
532574
public async Task CanAuthenticate_WithAuthorizationServerPathInsertionMetadata()
533575
{

tests/ModelContextProtocol.TestOAuthServer/Program.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ public Program(ILoggerProvider? loggerProvider = null, IConnectionListenerFactor
6767
/// </remarks>
6868
public bool ClientIdMetadataDocumentSupported { get; set; } = true;
6969

70+
/// <summary>
71+
/// Gets or sets a value indicating whether the authorization server requires a resource parameter.
72+
/// </summary>
73+
/// <remarks>
74+
/// The default value is <c>true</c>.
75+
/// </remarks>
76+
public bool RequireResource { get; set; } = true;
77+
7078
public HashSet<string> DisabledMetadataPaths { get; } = new(StringComparer.OrdinalIgnoreCase);
7179
public IReadOnlyCollection<string> MetadataRequests => _metadataRequests.ToArray();
7280

@@ -290,7 +298,7 @@ IResult HandleMetadataRequest(HttpContext context, string? issuerPath = null)
290298
}
291299

292300
// Validate resource in accordance with RFC 8707
293-
if (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource))
301+
if (RequireResource && (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource)))
294302
{
295303
return Results.Redirect($"{redirect_uri}?error=invalid_target&error_description=The+specified+resource+is+not+valid&state={state}");
296304
}
@@ -338,7 +346,7 @@ IResult HandleMetadataRequest(HttpContext context, string? issuerPath = null)
338346

339347
// Validate resource in accordance with RFC 8707
340348
var resource = form["resource"].ToString();
341-
if (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource))
349+
if (RequireResource && (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource)))
342350
{
343351
return Results.BadRequest(new OAuthErrorResponse
344352
{

0 commit comments

Comments
 (0)