.NET MCP Server OAuth with Microsoft Entra ID

Using Entra ID OAuth security with MCP servers.

In early July, the maintainers of the C# SDK for ModelContextProtocol library merged a substantial pull request implementing the OAuth standards that MCP servers can use. There's a great write-up you can find at this blog with more background and details.

Even with that blog and the samples in the csharp-sdk repo, I was hoping to see a clearer example that used Entra app registration.

What makes this a big deal?

Since MCP servers have been available, they originally were built and published with the intention for local use - simply communicating with a local process over stdio. As demand grew and the desire to share that functionality with more users or peers increased, deploying these services over HTTP has become the norm. However, the security layer wasn't obvious to implement because - frequently - engineers did not have control over the client communicating with the MCP server as well. These clients were often agent frameworks or local tools like Claude Desktop or Visual Studio Code.

But, now that the OAuth standard for MCP has been around for a few months, it's never been easier to get started adding authentication and authorization to your smallest services.

This is important because you may want to lock down certain tool calls to users who are a part of certain group membership, or audit who is calling which tools. These should be a cinch for any engineers who are already familiar with ASP.NET Core authentication and authorization middleware.

Show me the Code

Browse a runnable MCP server on GitHub that includes optional OAuth configuration:

GitHub - mitch-b/dotnet-mcp-template: MCP Server Template for .NET
MCP Server Template for .NET. Contribute to mitch-b/dotnet-mcp-template development by creating an account on GitHub.

To not overlap too much with Den's blog, I'll keep to configuration of Entra app registration and where that configuration needs to be set in the MCP server.

Microsoft Entra ID Configuration

Using Entra as our identity provider, navigate to your Entra ID directory page within the Azure Portal.

Within directory, create a new registration

Name your new registration appropriately, and you can skip the platform and redirect URI if you just want to secure your service and use an existing client (like VS Code or Claude Desktop).

After you've registered a new application, take a note of the Application (client) ID and Directory (tenant) ID values.

Don't worry, it's a fake tenant!

The last required step is heading to the Expose an API blade to setup a delegated scope user's will consent to in order to use your service:

First, click "Add" link to expose additional scopes. I left the default value which is the same as the Application (client) ID for ease of use.

Once that's set, let's add a scope named mcp.tools (change this as you need). I allowed users to request this scope and filled required fields before adding.

Now that we have our Entra configuration ready, let's ensure the MCP server is going to use that information to broadcast back to unauthenticated clients to encourage them to authenticate before proceeding.

ASP.NET Core Configuration

To enable Authentication and Authorization against the MCP endpoints, it uses the built-in ASP.NET Core middleware. Here's a complete Program.cs within your MCP server that's been simplified to emphasize security configuration:

Note the serverUrl, tenantId, appClientId, and scopeName configuration you can tweak for your use-case.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using ModelContextProtocol.AspNetCore.Authentication;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddProblemDetails();

var serverUrl = "http://localhost:5499/";
var tenantId = "9444defc-786a-46dd-942d-1e3307fa6691";
var appClientId = "122fb286-eb1c-48d6-b745-01b199119142";
var scopeName = "mcp.tools";

var authority = $"https://login.microsoftonline.com/{tenantId}/v2.0";

builder.Services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.Authority = authority;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidAudiences = [$"api://{appClientId}"],
        ValidIssuers = [$"https://sts.windows.net/{tenantId}/"],
        NameClaimType = "name",
        RoleClaimType = "roles",
    };
})
.AddMcp(options =>
{
    options.ResourceMetadata = new()
    {
        Resource = new Uri(serverUrl),
        AuthorizationServers = { new Uri(authority) },
        ScopesSupported = [$"api://{appClientId}/{scopeName}"],
    };
});

builder.Services.AddAuthorization();
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

app.UseExceptionHandler();
app.UseRouting();
app.MapDefaultEndpoints();

app.MapGet("/", () => "MCP server is running!");

app.UseAuthentication();
app.UseAuthorization();
app.MapMcp().RequireAuthorization();

app.Run();

Testing

We can also use the .vscode/mcp.json file to allow the built-in Agent chat in VSCode to use the locally running MCP server:

{
  "servers": {
    "McpTemplate-tools": {
      "url": "http://localhost:5499/",
      "dev": {},
      "type": "http"
    }
  },
  "inputs": []
}
I had to ensure it was http and not https for it to work within VS Code. Could be a trusted certificate issue with self-signed dev cert.

The dev property allows seeing more messages in VSCode Output window which could be useful.

This should open your default browser and prompt for a Microsoft account login.

Once you've authenticated, open the GitHub Copilot agent chat window and ensure tools are reloaded at the bottom.

Asking the agent, "get date and time from tool" reliably tried to run the get_date_time tool from the template server and I could see it working!

Quick Start

To get going with this template (.NET 9 SDK required), and provide your app registration values, try this:

git clone https://github.com/mitch-b/dotnet-mcp-template && cd dotnet-mcp-template

cd src/McpTemplate.AppHost

dotnet user-secrets set OAuth:Tenant 9444defc-786a-46dd-942d-1e3307fa6691
dotnet user-secrets set OAuth:Authority https://login.microsoftonline.com/9444defc-786a-46dd-942d-1e3307fa6691/v2.0

dotnet user-secrets set OAuth:Audience api://122fb286-eb1c-48d6-b745-01b199119142
dotnet user-secrets set OAuth:Scopes:0 api://122fb286-eb1c-48d6-b745-01b199119142/mcp.tools

dotnet run