Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e5957de
Using ICacheService in PostgresService, MySqlService and MarketplaceS…
anuchandy Oct 3, 2025
19d22c7
Use ICacheService in MonitorHealthModelService
anuchandy Oct 7, 2025
1e35cb4
Auth config support and token factory
anuchandy Oct 8, 2025
44dbcf3
designing HttpHostAuthentication
anuchandy Oct 9, 2025
981906c
Define marker TokenCredentialFactory to signal use CustomChainedCrede…
anuchandy Oct 9, 2025
1f86272
Updated BaseAzureService to accept ITokenCredentialFactory, use it an…
anuchandy Oct 9, 2025
568eeb4
remove extra new-lines (format)
anuchandy Oct 9, 2025
198fcec
Renaming ITokenCredentialFactory to ITokenCredentialProvider
anuchandy Oct 10, 2025
a742f9c
implementing TokenCredentialProviderFactory, all providers and wirein…
anuchandy Oct 10, 2025
ed5b667
Defining ServerConfiguration.Default, IHttpHostAuthenticationConfigur…
anuchandy Oct 10, 2025
3ef3984
Defining NoCacheService
anuchandy Oct 10, 2025
527ba30
Giving better names to credential providers: JwtPassthrough and JwtObo
anuchandy Oct 11, 2025
9c632ce
Improve namings, make it consistent acorss
anuchandy Oct 11, 2025
7427683
Sync with upstream main (adapt new types to ITokenCredentialProvider)
anuchandy Oct 12, 2025
014c1d9
Add user assigned MI support, make azureAd.ClientId optional for jwt+…
anuchandy Oct 13, 2025
0871dce
Use recent version of System.IdentityModel.Tokens.Jwt (8.14.0)
anuchandy Oct 13, 2025
fa94ee0
remove unsure parts
anuchandy Oct 13, 2025
d0d4cbd
revert obo
anuchandy Oct 14, 2025
d9190a3
Remove member variable caching from base service
anuchandy Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.1.25451.107" />
<PackageVersion Include="System.Formats.Asn1" Version="9.0.9" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.11.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.0-rc.1.25451.107" />
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.0" />
Expand All @@ -87,6 +87,8 @@
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.3.0" />
<PackageVersion Include="Microsoft.Extensions.Azure" Version="1.11.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.9" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageVersion Include="Microsoft.Identity.Web" Version="3.14.1" />
<PackageVersion Include="Azure.Search.Documents" Version="11.7.0-beta.7" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Areas.Server.Configuration;

namespace Azure.Mcp.Core.Areas.Server.Authentication.HttpHost;

/// <summary>
/// Factory for creating HTTP host authentication configuration implementations
/// based on the server configuration settings.
/// </summary>
public static class HttpHostAuthSetupFactory
{
/// <summary>
/// Creates an appropriate HTTP host authentication setup implementation
/// based on the provided server configuration.
/// </summary>
/// <param name="serverConfiguration">
/// The server configuration containing authentication settings.
/// If null or if inbound authentication is None, returns a no-op implementation.
/// </param>
/// <returns>
/// An implementation of IHttpHostAuthenticationConfiguration that handles
/// the authentication requirements specified in the server configuration.
/// </returns>
/// <exception cref="NotSupportedException">
/// Thrown when the authentication configuration combination is not supported.
/// </exception>
public static IHttpHostAuthSetup Create(ServerConfiguration serverConfiguration)
{
var inboundType = serverConfiguration.InboundAuthentication.Type;
if (inboundType == InboundAuthenticationType.None)
{
return IHttpHostAuthSetup.Default;
}

if (inboundType != InboundAuthenticationType.JwtBearerScheme)
{
throw new InvalidOperationException($"Only supported inbound authentication validation is JwtBearerScheme, but was {inboundType}");
}

var outboundType = serverConfiguration.OutboundAuthentication?.Type;
if (outboundType == OutboundAuthenticationType.ManagedIdentity)
{
return new JwtHttpHostAuthSetup(serverConfiguration);
}
else if (outboundType == OutboundAuthenticationType.JwtObo)
{
return new JwtOboHttpHostAuthSetup(serverConfiguration);
}
else if (outboundType == OutboundAuthenticationType.JwtPassthrough)
{
return new JwtHttpHostAuthSetup(serverConfiguration);
}

else
{
throw new NotSupportedException(
$"Outbound authentication type '{outboundType}' is not supported with JwtBearerScheme inbound authentication. " +
$"Supported types are: ManagedIdentity, BearerToken, OnBehalfOf.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;

namespace Azure.Mcp.Core.Areas.Server.Authentication.HttpHost;

/// <summary>
/// Sets up authentication for HTTP host scenarios in the Azure MCP Server.
/// Provides a unified interface for different authentication implementations
/// (None, JWT Bearer, On-Behalf-Of) to configure services, middleware, and endpoints.
/// </summary>
public interface IHttpHostAuthSetup
{
/// <summary>
/// Gets the default setup that performs no authentication setup.
/// </summary>
public static readonly IHttpHostAuthSetup Default = new DefaultSetup();

/// <summary>
/// Sets up authentication services in the dependency injection container.
/// This method is called during the service registration phase of HTTP host startup.
/// </summary>
/// <param name="services">The service collection to configure authentication services in.</param>
void SetupServices(IServiceCollection services);

/// <summary>
/// Sets up authentication middleware in the HTTP request pipeline.
/// This method is called during the middleware configuration phase, typically
/// adding UseAuthentication() and UseAuthorization() calls.
/// </summary>
/// <param name="app">The application builder to configure middleware pipeline.</param>
void SetupMiddleware(IApplicationBuilder app);

/// <summary>
/// Sets up authorization requirements on MCP endpoints.
/// This method is called during endpoint configuration to apply authorization
/// policies to the MCP protocol endpoints.
/// </summary>
/// <param name="mcpEndpoints">The MCP endpoint convention builder to configure authorization on.</param>
void SetupEndpoints(IEndpointConventionBuilder mcpEndpoints);

/// <summary>
/// Default no-operation setup that performs no authentication setup.
/// This implementation maintains open endpoints without any authentication requirements.
/// </summary>
private sealed class DefaultSetup : IHttpHostAuthSetup
{
/// <summary>
/// No authentication services. This is a no-operation implementation.
/// </summary>
/// <param name="services">The service collection (not modified).</param>
public void SetupServices(IServiceCollection services)
{
// No authentication services to configure
// This maintains the current behavior where authentication is disabled
}

/// <summary>
/// No authentication middleware. This is a no-operation implementation.
/// </summary>
/// <param name="app">The application builder (not modified).</param>
public void SetupMiddleware(IApplicationBuilder app)
{
// No authentication middleware to configure
// UseAuthentication() and UseAuthorization() are not called
}

/// <summary>
/// No endpoint authorization. This is a no-operation implementation.
/// </summary>
/// <param name="mcpEndpoints">The MCP endpoint convention builder (not modified).</param>
public void SetupEndpoints(IEndpointConventionBuilder mcpEndpoints)
{
// No authorization requirements on endpoints
// RequireAuthorization() is not called, endpoints remain open
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.IdentityModel.Tokens.Jwt;
using Azure.Mcp.Core.Areas.Server.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;

namespace Azure.Mcp.Core.Areas.Server.Authentication.HttpHost;

/// <summary>
/// JWT Bearer authentication configuration for HTTP host scenarios.
/// This implementation sets up JWT token validation for incoming requests and configures
/// the necessary middleware and authorization policies.
/// </summary>
internal sealed class JwtHttpHostAuthSetup : IHttpHostAuthSetup
{
private readonly ServerConfiguration _serverConfiguration;

/// <summary>
/// Initializes a new instance of the JwtBearerAuthentication class.
/// </summary>
/// <param name="serverConfiguration">The server configuration containing authentication settings.</param>
/// <exception cref="ArgumentNullException">Thrown when serverConfiguration is null.</exception>
/// <remarks>
/// This constructor assumes the configuration is valid for JWT Bearer authentication.
/// Use HttpHostAuthenticationConfigurationFactory.Create() to ensure proper instantiation.
/// </remarks>
public JwtHttpHostAuthSetup(ServerConfiguration serverConfiguration)
{
_serverConfiguration = serverConfiguration ?? throw new ArgumentNullException(nameof(serverConfiguration));
}

/// <summary>
/// Sets up JWT Bearer authentication services including token validation,
/// authorization policies, forwarded headers, and HTTP context accessor.
/// </summary>
/// <param name="services">The service collection to configure authentication services in.</param>
public void SetupServices(IServiceCollection services)
{
var azureAd = _serverConfiguration.InboundAuthentication.AzureAd!;

// Configure forwarded headers for reverse proxy scenarios (Container Apps, Kubernetes, etc.)
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});

services.AddHttpContextAccessor();

JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = $"{azureAd.Instance.TrimEnd('/')}/{azureAd.TenantId}/v2.0";

options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = $"{azureAd.Instance.TrimEnd('/')}/{azureAd.TenantId}/v2.0",

ValidateAudience = true,
ValidAudiences = GetValidAudiences(azureAd),

ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(2),
RoleClaimType = "roles"
};

options.MapInboundClaims = false;
options.RefreshOnIssuerKeyNotFound = true;
});

services.AddAuthorization(options =>
{
if (azureAd.RequiredRoles?.Length > 0)
{
options.AddPolicy("McpRolePolicy", p => p.RequireRole(azureAd.RequiredRoles));
}

options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}

/// <summary>
/// Sets up authentication and authorization middleware in the HTTP pipeline.
/// Adds UseForwardedHeaders, UseAuthentication, and UseAuthorization middleware.
/// </summary>
/// <param name="app">The application builder to configure middleware pipeline.</param>
public void SetupMiddleware(IApplicationBuilder app)
{
app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();
}

/// <summary>
/// Sets up authorization requirements on MCP endpoints.
/// Requires authentication and optionally role validation for all MCP protocol endpoints.
/// </summary>
/// <param name="mcpEndpoints">The MCP endpoint convention builder to configure authorization on.</param>
public void SetupEndpoints(IEndpointConventionBuilder mcpEndpoints)
{
var azureAd = _serverConfiguration.InboundAuthentication.AzureAd!;

if (azureAd.RequiredRoles?.Length > 0)
{
// Uses the role policy to enforce role requirements
mcpEndpoints.RequireAuthorization("McpRolePolicy");
}
else
{
// Otherwise falls back to just requiring authentication
mcpEndpoints.RequireAuthorization();
}
}

/// <summary>
/// Gets the valid audiences for JWT token validation based on Azure AD configuration.
/// </summary>
/// <param name="azureAd">The Azure AD configuration containing client ID and audience information.</param>
/// <returns>An array of valid audience strings for token validation.</returns>
private static string[] GetValidAudiences(AzureAdConfig azureAd)
{
var audiences = new List<string> { azureAd.Audience };

// Only include ClientId-based audiences if ClientId is provided
if (!string.IsNullOrWhiteSpace(azureAd.ClientId))
{
audiences.Add(azureAd.ClientId);
audiences.Add($"api://{azureAd.ClientId}");
}

return audiences.Distinct().Where(a => !string.IsNullOrWhiteSpace(a)).ToArray();
}
}
Loading
Loading