Skip to content

Commit 31f611a

Browse files
committed
Multi-scheme support
1 parent d4cc3ad commit 31f611a

File tree

4 files changed

+89
-8
lines changed

4 files changed

+89
-8
lines changed

samples/ProtectedMCPClient/BasicOAuthAuthorizationProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class BasicOAuthAuthorizationProvider(
3333
private AuthorizationServerMetadata? _authServerMetadata;
3434

3535
/// <inheritdoc />
36-
public IEnumerable<string> SupportedSchemes => new[] { "DPoP" };
36+
public IEnumerable<string> SupportedSchemes => new[] { "Bearer" };
3737

3838
/// <inheritdoc />
3939
public Task<string?> GetCredentialAsync(string scheme, Uri resourceUri, CancellationToken cancellationToken = default)

samples/ProtectedMCPServer/Program.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
OnChallenge = context =>
5252
{
5353
Console.WriteLine($"Challenging client to authenticate with Entra ID");
54+
55+
// Skip the default Bearer header - MCP handler will provide the complete one
56+
context.HandleResponse();
57+
5458
return Task.CompletedTask;
5559
}
5660
};
@@ -72,6 +76,34 @@
7276

7377
return metadata;
7478
};
79+
80+
// Specify authentication schemes that this server supports
81+
options.SupportedAuthenticationSchemes.Add("Bearer");
82+
options.SupportedAuthenticationSchemes.Add("Basic");
83+
84+
// For a server that doesn't want to support Bearer, you would simply not add it:
85+
// options.SupportedAuthenticationSchemes.Add("Basic");
86+
// options.SupportedAuthenticationSchemes.Add("Digest");
87+
88+
// You can also use the dynamic provider for more flexible scheme selection:
89+
/*
90+
options.SupportedAuthenticationSchemesProvider = context =>
91+
{
92+
// You can use context information to determine which schemes to offer
93+
var schemes = new List<string>();
94+
95+
// Add Bearer for most clients
96+
schemes.Add("Bearer");
97+
98+
// Example of conditional scheme based on client type or other factors
99+
if (context.Request.Headers.UserAgent.ToString().Contains("SpecialClient"))
100+
{
101+
schemes.Add("Basic");
102+
}
103+
104+
return schemes;
105+
};
106+
*/
75107
});
76108

77109
builder.Services.AddAuthorization(options =>

src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenticationHandler.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,28 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
132132
// Initialize properties if null
133133
properties ??= new AuthenticationProperties();
134134

135-
// Set the WWW-Authenticate header with the resource_metadata
136-
string headerValue = $"Bearer realm=\"{Scheme.Name}\"";
137-
headerValue += $", resource_metadata=\"{rawPrmDocumentUri}\"";
138-
139-
Response.Headers["WWW-Authenticate"] = headerValue;
140-
141135
// Store the resource_metadata in properties in case other handlers need it
142136
properties.Items["resource_metadata"] = rawPrmDocumentUri;
143137

138+
// Get supported schemes from the options
139+
var options = _optionsMonitor.CurrentValue;
140+
var supportedSchemes = options.GetSupportedAuthenticationSchemes(Request.HttpContext).ToList();
141+
142+
// If no schemes are explicitly defined, don't add any WWW-Authenticate headers
143+
if (supportedSchemes.Count == 0)
144+
{
145+
return base.HandleChallengeAsync(properties);
146+
}
147+
148+
// Add headers for each supported authentication scheme
149+
foreach (var scheme in supportedSchemes)
150+
{
151+
// For all schemes, include the realm and resource metadata
152+
// This allows discovery of OAuth capabilities regardless of the authentication scheme
153+
string headerValue = $"{scheme} realm=\"{Scheme.Name}\", resource_metadata=\"{rawPrmDocumentUri}\"";
154+
Response.Headers.Append("WWW-Authenticate", headerValue);
155+
}
156+
144157
return base.HandleChallengeAsync(properties);
145158
}
146159
}

src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenticationOptions.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public class McpAuthenticationOptions : AuthenticationSchemeOptions
2424
/// The URI to the resource metadata document.
2525
/// </summary>
2626
/// <remarks>
27-
/// This URI will be included in the WWW-Authenticate header when a 401 response is returned.
27+
/// This URI will be included in the WWW-Authenticate header when a 401 response is returned
28+
/// and Bearer authentication is supported.
2829
/// </remarks>
2930
public Uri ResourceMetadataUri { get; set; } = DefaultResourceMetadataUri;
3031

@@ -48,6 +49,26 @@ public class McpAuthenticationOptions : AuthenticationSchemeOptions
4849
/// </remarks>
4950
public Func<HttpContext, ProtectedResourceMetadata>? ResourceMetadataProvider { get; set; }
5051

52+
/// <summary>
53+
/// Gets or sets the authentication schemes supported by this server.
54+
/// </summary>
55+
/// <remarks>
56+
/// When set, these schemes will be included in WWW-Authenticate headers during an authentication challenge.
57+
/// By default, this is empty and must be populated with the authentication schemes your server supports.
58+
/// If Bearer is included, the resource metadata URI will be included in its parameters.
59+
/// </remarks>
60+
public List<string> SupportedAuthenticationSchemes { get; set; } = new List<string>();
61+
62+
/// <summary>
63+
/// Gets or sets a delegate that dynamically provides authentication schemes based on the HTTP context.
64+
/// </summary>
65+
/// <remarks>
66+
/// When set, this delegate will be called to determine which authentication schemes to include
67+
/// in WWW-Authenticate headers during an authentication challenge. This takes precedence over the static
68+
/// <see cref="SupportedAuthenticationSchemes"/> property.
69+
/// </remarks>
70+
public Func<HttpContext, IEnumerable<string>>? SupportedAuthenticationSchemesProvider { get; set; }
71+
5172
/// <summary>
5273
/// Gets the resource metadata for the current request.
5374
/// </summary>
@@ -62,4 +83,19 @@ internal ProtectedResourceMetadata GetResourceMetadata(HttpContext context)
6283

6384
return ResourceMetadata;
6485
}
86+
87+
/// <summary>
88+
/// Gets the supported authentication schemes for the current request.
89+
/// </summary>
90+
/// <param name="context">The HTTP context for the current request.</param>
91+
/// <returns>The authentication schemes supported for the current request.</returns>
92+
internal IEnumerable<string> GetSupportedAuthenticationSchemes(HttpContext context)
93+
{
94+
if (SupportedAuthenticationSchemesProvider != null)
95+
{
96+
return SupportedAuthenticationSchemesProvider(context);
97+
}
98+
99+
return SupportedAuthenticationSchemes;
100+
}
65101
}

0 commit comments

Comments
 (0)