Skip to content

WIP: Authorization Support (Using ASP.NET Core Native AuthN/AuthZ Integration) #377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 52 commits into
base: main
Choose a base branch
from

Conversation

localden
Copy link
Collaborator

@localden localden commented May 2, 2025

Implements the authorization flow for clients and servers, per specification. Instead of re-implementing everything from scratch, this follows the suggestions from #349 and uses the native ASP.NET Core constructs to handle post-discovery steps server-side.

@localden localden marked this pull request as draft May 2, 2025 06:38
// Add authorization policy for MCP
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("McpAuth", policy =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be where an AddMcpPolicy would be handy. It could preconfigure RequireAuthenticatedUser and AddAuthenticationSchemes(McpAuthenticationDefaults.AuthenticationScheme) and then give the policy builder to your callback for further customization.

// Set up the resource URI if not already configured, using the current request as a fallback
if (Options.ResourceMetadata.Resource == null)
{
Options.ResourceMetadata.Resource = new Uri($"{Request.Scheme}://{Request.Host}");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes the server is hosted at the root of the domain, which is not always the case. It may also need {Request.PathBase} or something similar


// Set the WWW-Authenticate header with the resource_metadata
string headerValue = $"Bearer realm=\"{Scheme.Name}\"";
headerValue += $", resource_metadata=\"{metadataUrl}\"";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the metadata url needs to be escaped

resource_metadata=\"{Uri.EscapeDataString(metadataUri.ToString())}\"

/// This contains the OAuth metadata for the protected resource, including authorization servers,
/// supported scopes, and other information needed for clients to authenticate.
/// </remarks>
public ProtectedResourceMetadata ResourceMetadata { get; set; } = new ProtectedResourceMetadata();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be an option to return this dynamically so that the metadata can change based upon incoming callers.

config.Scopes);

// Attach the access token to future requests
httpClient.AttachToken(tokenResponse.AccessToken);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this may not handle token expiry and refresh, as in the token could expire and get stuck on the http client instance, never succeeding after expiration.

/// </summary>
public static class OAuthAuthorizationHelpers
{
private static readonly HttpClient _httpClient = new();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can lead to outages. IHttpClientFactory needs to be used to follow best practices. Sockets sometimes get into a bad state and need to be recycled. That's why periodic recycling is handled by the http client factory internally.

/// <summary>
/// Provides helper methods for handling OAuth authorization.
/// </summary>
public static class OAuthAuthorizationHelpers

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be turned into an instance type with an interface, registered as a singleton so that its implementation can be replaced by more secure and/or custom ones.

{
// Create a temporary HttpClient to handle the authentication
// We need to use a new client to avoid infinite recursion
using var httpClient = new HttpClient();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a new http client should not be created within a delegating handler. Use base.SendAsync to make other network calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants