using System.Security.Claims; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using ModelContextProtocol.AspNetCore.Authentication; using ModelContextProtocol.Authentication; namespace BobMcp.Server.Auth; public static class AuthenticationExtensions { public const string PolicyName = "BobiAccess"; public static void AddBobiAuth(this IServiceCollection services) { var issuer = RequireEnv("AUTH_ISSUER"); var resource = RequireEnv("MCP_RESOURCE"); var allowedEmails = RequireEnv("ALLOWED_EMAILS") .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .ToHashSet(StringComparer.OrdinalIgnoreCase); if (allowedEmails.Count == 0) { throw new InvalidOperationException( "ALLOWED_EMAILS must list at least one address."); } services .AddAuthentication(options => { options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = issuer; options.MapInboundClaims = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = issuer, ValidateAudience = true, ValidAudience = resource, ValidateLifetime = true, ValidateIssuerSigningKey = true, NameClaimType = "email", }; }) .AddMcp(options => { options.ResourceMetadata = new ProtectedResourceMetadata { Resource = resource, AuthorizationServers = { issuer }, ScopesSupported = ["openid", "email"], }; }); services .AddAuthorizationBuilder() .AddPolicy(PolicyName, policy => policy.RequireAssertion(context => { var email = context.User.FindFirstValue("email"); return email is not null && allowedEmails.Contains(email); })); } private static string RequireEnv(string name) { var value = Environment.GetEnvironmentVariable(name); if (string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException( $"{name} is required. Authentication is mandatory; the server will not " + "start without it."); } return value; } }