Skip to content

Commit 9582111

Browse files
committed
Update based on feedback
1 parent 3713da6 commit 9582111

File tree

6 files changed

+134
-18
lines changed

6 files changed

+134
-18
lines changed

samples/SecureWeatherServer/Program.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,14 @@
7070
.WithAuthorization(metadata =>
7171
{
7272
metadata.AuthorizationServers.Add(new Uri("https://login.microsoftonline.com/a2213e1c-e51e-4304-9a0d-effe57f31655/v2.0"));
73-
metadata.ScopesSupported.AddRange(["weather.read", "weather.write"]);
7473
metadata.BearerMethodsSupported.Add("header");
74+
metadata.ScopesSupported.AddRange(["weather.read", "weather.write"]);
75+
76+
// Add optional documentation
7577
metadata.ResourceDocumentation = new Uri("https://docs.example.com/api/weather");
7678
});
7779

7880
// Configure authentication using the built-in authentication system
79-
// Register "Bearer" scheme with our SimpleAuthHandler and set it as the default scheme
8081
builder.Services.AddAuthentication(options =>
8182
{
8283
options.DefaultScheme = "Bearer";
@@ -96,8 +97,8 @@
9697

9798
var app = builder.Build();
9899

99-
// Set up the middleware pipeline
100100
app.UseAuthentication();
101+
app.UseMcpAuthenticationResponse();
101102
app.UseAuthorization();
102103

103104
// Map MCP endpoints with authorization
@@ -123,17 +124,12 @@
123124
// In a real app, you'd use a JWT handler or other proper authentication
124125
class SimpleAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
125126
{
126-
// Directly inject the ResourceMetadataService instead of the options
127-
private readonly ResourceMetadataService _resourceMetadataService;
128-
129127
public SimpleAuthHandler(
130128
IOptionsMonitor<AuthenticationSchemeOptions> options,
131129
ILoggerFactory logger,
132-
UrlEncoder encoder,
133-
ResourceMetadataService resourceMetadataService)
130+
UrlEncoder encoder)
134131
: base(options, logger, encoder)
135132
{
136-
_resourceMetadataService = resourceMetadataService;
137133
}
138134

139135
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
@@ -176,16 +172,8 @@ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
176172

177173
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
178174
{
179-
// Always include the resource_metadata in the WWW-Authenticate header
180-
var baseUrl = $"{Request.Scheme}://{Request.Host}";
181-
var metadataUrl = $"{baseUrl}/.well-known/oauth-protected-resource";
182-
183-
// Add WWW-Authenticate header with resource_metadata
184-
Response.Headers.WWWAuthenticate = $"Bearer resource_metadata=\"{metadataUrl}\"";
185-
186-
// Set 401 status code
175+
// No need to manually set WWW-Authenticate header anymore - handled by middleware
187176
Response.StatusCode = 401;
188-
189177
return Task.CompletedTask;
190178
}
191179
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Http;
3+
using ModelContextProtocol.Auth;
4+
5+
namespace ModelContextProtocol.AspNetCore.Auth;
6+
7+
/// <summary>
8+
/// Middleware that attaches WWW-Authenticate headers with resource metadata to 401 responses.
9+
/// </summary>
10+
public class McpAuthenticationResponseMiddleware
11+
{
12+
private readonly RequestDelegate _next;
13+
private readonly ResourceMetadataService _resourceMetadataService;
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="McpAuthenticationResponseMiddleware"/> class.
17+
/// </summary>
18+
/// <param name="next">The next request delegate in the pipeline.</param>
19+
/// <param name="resourceMetadataService">The resource metadata service.</param>
20+
public McpAuthenticationResponseMiddleware(RequestDelegate next, ResourceMetadataService resourceMetadataService)
21+
{
22+
_next = next;
23+
_resourceMetadataService = resourceMetadataService;
24+
}
25+
26+
/// <summary>
27+
/// Processes the request and adds WWW-Authenticate headers to 401 responses.
28+
/// </summary>
29+
/// <param name="context">The HTTP context.</param>
30+
/// <returns>A task representing the asynchronous operation.</returns>
31+
public async Task InvokeAsync(HttpContext context)
32+
{
33+
// Add a callback to the OnStarting event which fires before the response headers are sent
34+
context.Response.OnStarting(() =>
35+
{
36+
// Check if the response is a 401 Unauthorized
37+
if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
38+
{
39+
// Get the base URL of the request
40+
var baseUrl = $"{context.Request.Scheme}://{context.Request.Host}";
41+
var metadataPath = "/.well-known/oauth-protected-resource";
42+
var metadataUrl = $"{baseUrl}{metadataPath}";
43+
44+
// Add or update the WWW-Authenticate header
45+
if (!context.Response.Headers.ContainsKey("WWW-Authenticate") ||
46+
!context.Response.Headers["WWW-Authenticate"].ToString().Contains("resource_metadata"))
47+
{
48+
context.Response.Headers.WWWAuthenticate = $"Bearer resource_metadata=\"{metadataUrl}\"";
49+
}
50+
}
51+
return Task.CompletedTask;
52+
});
53+
54+
// Call the next delegate/middleware in the pipeline
55+
await _next(context);
56+
}
57+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.AspNetCore.Builder;
2+
3+
namespace ModelContextProtocol.AspNetCore.Auth;
4+
5+
/// <summary>
6+
/// Extension methods for the McpAuthenticationResponseMiddleware.
7+
/// </summary>
8+
public static class McpAuthenticationResponseMiddlewareExtensions
9+
{
10+
/// <summary>
11+
/// Adds the MCP authentication response middleware to the application pipeline.
12+
/// </summary>
13+
/// <param name="builder">The application builder.</param>
14+
/// <returns>The application builder for chaining.</returns>
15+
public static IApplicationBuilder UseWwwAuthenticateHeaderMiddleware(this IApplicationBuilder builder)
16+
{
17+
return builder.UseMiddleware<McpAuthenticationResponseMiddleware>();
18+
}
19+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace ModelContextProtocol.AspNetCore.Auth;
5+
6+
/// <summary>
7+
/// Extension methods for WebApplication to add MCP-specific middleware.
8+
/// </summary>
9+
public static class McpWebApplicationExtensions
10+
{
11+
/// <summary>
12+
/// Adds the MCP authentication response middleware to the application pipeline.
13+
/// This middleware automatically adds the resource_metadata field to WWW-Authenticate headers in 401 responses.
14+
/// </summary>
15+
/// <remarks>
16+
/// This middleware should be registered AFTER UseAuthentication() but BEFORE UseAuthorization().
17+
/// </remarks>
18+
/// <param name="app">The web application.</param>
19+
/// <returns>The web application for chaining.</returns>
20+
public static IApplicationBuilder UseMcpAuthenticationResponse(this IApplicationBuilder app)
21+
{
22+
if (app.ApplicationServices.GetService<McpAuthenticationResponseMarker>() == null)
23+
{
24+
throw new InvalidOperationException(
25+
"McpAuthenticationResponseMarker service is not registered. " +
26+
"Make sure you call AddMcpServer().WithAuthorization() first.");
27+
}
28+
29+
return app.UseMiddleware<McpAuthenticationResponseMiddleware>();
30+
}
31+
}

src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ public static IMcpServerBuilder WithAuthorization(
8787
});
8888
});
8989

90+
// Register the middleware for automatically adding WWW-Authenticate headers
91+
// Store in DI that we need to use the middleware
92+
builder.Services.AddSingleton<McpAuthenticationResponseMarker>();
93+
9094
return builder;
9195
}
9296
}
@@ -95,3 +99,8 @@ public static IMcpServerBuilder WithAuthorization(
9599
/// Marker class to indicate that MCP authorization has been configured.
96100
/// </summary>
97101
internal class McpAuthorizationMarker { }
102+
103+
/// <summary>
104+
/// Marker class to indicate that MCP authentication response middleware should be used.
105+
/// </summary>
106+
internal class McpAuthenticationResponseMarker { }

src/ModelContextProtocol.AspNetCore/McpEndpointRouteBuilderExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ public static IEndpointConventionBuilder MapMcp(this IEndpointRouteBuilder endpo
5656
var authMarker = endpoints.ServiceProvider.GetService<McpAuthorizationMarker>();
5757
if (authMarker != null)
5858
{
59+
// Check if the authentication response middleware is configured
60+
var authResponseMarker = endpoints.ServiceProvider.GetService<McpAuthenticationResponseMarker>();
61+
if (authResponseMarker != null)
62+
{
63+
// Register the middleware to automatically add WWW-Authenticate headers to 401 responses
64+
// We need to add this middleware to the parent app, not just the endpoint group
65+
if (endpoints is IApplicationBuilder app)
66+
{
67+
app.UseWwwAuthenticateHeaderMiddleware();
68+
}
69+
}
70+
5971
// Authorization is configured, so automatically map the OAuth protected resource endpoint
6072
var resourceMetadataService = endpoints.ServiceProvider.GetRequiredService<ResourceMetadataService>();
6173

0 commit comments

Comments
 (0)