Implement a secure MCP OAuth desktop client using OAuth and Entra ID

The article demonstrates how to implement a secure MCP OAuth desktop client using Microsoft Entra ID. The MCP server is built with ASP.NET Core and secured using Microsoft Entra ID. The MCP client is a .NET console application that must acquire an OAuth access token to interact with the MCP server.

Code: https://github.com/damienbod/McpSecurity

Setup

A client application was developed to authenticate and interact with an MCP server secured by Microsoft Entra ID. It uses Azure OpenAI and obtains a delegated access token via a public OpenID Connect client. The application then calls a function exposed by the MCP server. The server is a simple ASP.NET Core application that implements the MCP Server API.

MCP Server

The MCP server from the previous post “Implement a secure MCP server using OAuth and Entra ID” is used by the new client. It only accepts delegated access tokens from Microsoft Entra ID for a specific tenant.

MCP desktop OAuth client

The Microsoft.Identity.Client Nuget package is used to implement the public OpenID Connect client. As a desktop app, or the console application used in this demo cannot keep a secret, a public client is required and the client must use a delegated access token based on the user. OpenID Connect Code flow with PKCE is used to authenticate and the client must use a web browser to authenticate the user. App-to-App services cannot be used for this type of application, we have a low trust.

The SignInUserAndGetTokenUsingMSAL method is used to implement the user authentication.

private static async Task<string> SignInUserAndGetTokenUsingMSAL(
    PublicClientApplicationOptions configuration, string[] scopes)
{
    string authority = string.Concat(configuration.Instance, configuration.TenantId);

    // Initialize the MSAL library by building a public client application
    application = PublicClientApplicationBuilder.Create(configuration.ClientId)
                    .WithAuthority(authority)
                    .WithDefaultRedirectUri()
                    .Build();

    AuthenticationResult result;
    try
    {
        var accounts = await application.GetAccountsAsync();
        result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
         .ExecuteAsync();
    }
    catch (MsalUiRequiredException ex)
    {
        result = await application.AcquireTokenInteractive(scopes)
         .WithClaims(ex.Claims)
         .ExecuteAsync();
    }

    return result.AccessToken;
}

The CreateMcpTransportAsync method uses the Microsoft Entra ID client to acquire the access token and sets the Authorization header in the HttpClient with the Bearer token. This token is validated on the MCP server. The server expects a delegated access token with the “mcp:tools” scp claim. No application access tokens are accepted.

private static PublicClientApplicationOptions? appConfiguration = null;

// The MSAL Public client app
private static IPublicClientApplication? application;

public static async Task<IClientTransport> CreateMcpTransportAsync(HttpClient httpClient, IConfigurationRoot configuration)
{
    appConfiguration = configuration.Get<PublicClientApplicationOptions>();
    string[] scopes = ["api://96b0f495-3b65-4c8f-a0c6-c3767c3365ed/mcp:tools"];

    // Sign-in user using MSAL and obtain an access token for MS Graph
    var accessToken = await SignInUserAndGetTokenUsingMSAL(appConfiguration!, scopes);

    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    var httpMcpServer = configuration["HttpMcpServerUrl"];
    var transport = new SseClientTransport(new()
    {
        Endpoint = new Uri(httpMcpServer!),
        Name = "MCP Desktop Client",
    }, httpClient);

    return transport;
}

When the applications are started, the console application opens up the browser to authenticate.

Notes

With this setup, user delegated access tokens are forced all the way. No App-to-App security is possible. The MCP Server is used to integrated into the client application as a remote server. DCR is not used in the setup and should not be used in this setup. Trust is the main problem with supporting DCR. The security can still be further improved and there are some interesting OAuth issues open about this.

Links

https://mcp.azure.com/

https://github.com/microsoft/azure-devops-mcp

https://auth0.com/blog/an-introduction-to-mcp-and-authorization/

https://learning.postman.com/docs/postman-ai-agent-builder/mcp-server-flows/mcp-server-flows/

https://stytch.com/blog/MCP-authentication-and-authorization-guide/

.NET MCP server

https://learn.microsoft.com/en-us/dotnet/ai/quickstarts/build-mcp-server

Standards, draft Standards

OAuth 2.0 Dynamic Client Registration Protocol

OAuth 2.0 Authorization Server Metadata

https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization

https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices

https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1299

https://den.dev/blog/mcp-authorization-resource/

SPIFFE

https://spiffe.io/docs/latest/spiffe-about/overview/

Azure Open AI

https://learn.microsoft.com/en-us/azure/ai-foundry/

One comment

  1. […] Implement a secure MCP OAuth desktop client using OAuth and Entra ID (Damien Bowden) […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.