Skip to content

OAuth authentication request fails with MS Entra-ID #648

Description

@mbuchberger1967

Hi, I just checked out the 0.3.0-preview.3 release and I modified the ProtectedMCPServer and ProtectedMCPClient samples to use MS Entra-ID as OAUTH server (see code snippets below):
The URL that is created by the mcp server for starting the OAUTH flow looks like this:

https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize?client_id=xxxxx&redirect_uri=http%3a%2f%2flocalhost%3a1179%2fcallback&response_type=code&code_challenge=q09KNTJ_gyVsLnvjSSX46MYl_DcPTuNhosF6U5n9KOs&code_challenge_method=S256&resource=http%3a%2f%2flocalhost%3a7077%2f&scope=api%3a%2f%xxxx-xxxxx-xxxxx%2fmcp.tools

This url contains a resource and a scope parameter.
But MS Entra-ID complains about the resource parameter, as in OAuth 2.0 it should be the scope parameter used:
AADSTS901002: The 'resource' request parameter is not supported.

What is your idea or hint to solve this problem?

My changes:
Client: I added the ClientId which I configured in Azure portal

var transport = new SseClientTransport(new()
{
    Endpoint = new Uri(serverUrl),
    Name = "Secure Weather Client",
    OAuth = new()
    {
        ClientName = "ProtectedMcpClient",
        ClientId = "xxxx-xxxxx-xxxx-xxxx",
        RedirectUri = new Uri("http://localhost:1179/callback"),
        AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
    }
}, httpClient, consoleLoggerFactory);

Server:
changed the OAuth server url:
var inMemoryOAuthServerUrl = "https://login.microsoftonline.com/<tenant-id>/v2.0";

changed ScopesSupported analog to the Azure Portal definition:
ScopesSupported = ["api://7e7eaf63-375a-4a0d-9872-574fdb5e08d6/mcp.tools"],

Thanks
Markus

Activity

  1. eusebiu commented on Jul 27, 2025

    @eusebiu

    I get that too! According to https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow the resource is not part of the request.

    I think this part should be as simple as MSAL - on the server just use the existing ASP.NET Authentication & Authorization mechanisms & UX (e.g. [Authorize] attribute with a role over the MCP Server/Tool - e.g. I would like to separate the role of agent/AI vs user roles) and on the client side, simply use MSAL to get the token (e.g. authorization flow, client secret, device code, etc.) and add it to the protocol as we do for HttpClient (Authorization header: Bearer token).

    The MCP client should not perform any OAuth functionalities directly like opening the browser, getting the token as there are libraries for that way much better coded/tested.

  2. eusebiu commented on Jul 27, 2025

    @eusebiu

    I got it to work (i.e. the authentication) using this workaround (i.e. by removing the resource and adding the grant_type) but I get an exception when creating the MCP client - essentially for the same reason (resource is being sent -

    ["resource"] = protectedResourceMetadata.Resource.ToString(),
    ).

    // on server
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
    })
    .AddMcp(options =>
    {
        options.ResourceMetadata = new()
        {
            Resource = new Uri("http://localhost:53969/sse"),
            AuthorizationServers = { new Uri("https://login.microsoftonline.com/<tenant>/v2.0") },
            ScopesSupported = ["some_scope"], // from Entra App Registration
            ResourceName = "MCP API",
        };
    })
    .AddMicrosoftIdentityWebApi(configuration.GetSection("Authentication"));
    
    // in client
    var transport = new SseClientTransport(new()
    {
        Endpoint = new Uri("http://localhost:53969/sse"),
        Name = "Secure MCP Client",
    
        OAuth = new()
        {
            ClientName = "API MCP Client",
            ClientId = "xxx",
            RedirectUri = new Uri("http://localhost:8080"), // correct redirect URL from Entra App Registration
            AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
        }
    }, loggerFactory);
    
    // FAILS HERE - see below
    var mcpClient = await McpClientFactory.CreateAsync(
        transport,
    	loggerFactory: loggerFactory);
    
    static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
    {
        Console.WriteLine("Starting OAuth authorization flow...");
    
        // remove 'resource' query parameter if present
        var uriBuilder = new UriBuilder(authorizationUrl);
        var query = HttpUtility.ParseQueryString(uriBuilder.Query);
        query.Remove("resource");
        uriBuilder.Query = query.ToString();
    
        // add 'grant_type' query parameter if not present
        if (!query.AllKeys.Contains("grant_type"))
        {
            query["grant_type"] = "authorization_code";
            uriBuilder.Query = query.ToString();
        }
        authorizationUrl = uriBuilder.Uri;
    
        Console.WriteLine($"Opening browser to: {authorizationUrl}");
        // rest of the code
    }
    

    Exception:

    Listening for OAuth callback on: http://localhost:8080/
    Authorization code received successfully.
    warn: ModelContextProtocol.Client.SseClientSessionTransport[1753402347]
          Secure  API MCP Client transport message reading failed.
          System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
             at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
             at ModelContextProtocol.Authentication.ClientOAuthProvider.FetchTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.ExchangeCodeForTokenAsync(ProtectedResourceMetadata protectedResourceMetadata, AuthorizationServerMetadata authServerMetadata, String authorizationCode, String codeVerifier, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.InitiateAuthorizationCodeFlowAsync(ProtectedResourceMetadata protectedResourceMetadata, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)         at ModelContextProtocol.Authentication.ClientOAuthProvider.PerformOAuthAuthorizationAsync(HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.HandleUnauthorizedResponseAsync(String scheme, HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.AuthenticatingMcpHttpClient.HandleUnauthorizedResponseAsync(HttpRequestMessage originalRequest, JsonRpcMessage originalJsonRpcMessage, HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.AuthenticatingMcpHttpClient.SendAsync(HttpRequestMessage request, JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.SseClientSessionTransport.ReceiveMessagesAsync(CancellationToken cancellationToken)
    fail: ModelContextProtocol.Client.SseClientSessionTransport[1985454809]
          Secure  API MCP Client transport connect failed.
          System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
             at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
             at ModelContextProtocol.Authentication.ClientOAuthProvider.FetchTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.ExchangeCodeForTokenAsync(ProtectedResourceMetadata protectedResourceMetadata, AuthorizationServerMetadata authServerMetadata, String authorizationCode, String codeVerifier, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.InitiateAuthorizationCodeFlowAsync(ProtectedResourceMetadata protectedResourceMetadata, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)         at ModelContextProtocol.Authentication.ClientOAuthProvider.PerformOAuthAuthorizationAsync(HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.HandleUnauthorizedResponseAsync(String scheme, HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.AuthenticatingMcpHttpClient.HandleUnauthorizedResponseAsync(HttpRequestMessage originalRequest, JsonRpcMessage originalJsonRpcMessage, HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.AuthenticatingMcpHttpClient.SendAsync(HttpRequestMessage request, JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.SseClientSessionTransport.ReceiveMessagesAsync(CancellationToken cancellationToken)
             at ModelContextProtocol.Client.SseClientSessionTransport.ConnectAsync(CancellationToken cancellationToken)
    fail: ModelContextProtocol.Client.McpClient[1155727496]
          Secure  API MCP Client client initialization error.
          System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
             at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
             at ModelContextProtocol.Authentication.ClientOAuthProvider.FetchTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.ExchangeCodeForTokenAsync(ProtectedResourceMetadata protectedResourceMetadata, AuthorizationServerMetadata authServerMetadata, String authorizationCode, String codeVerifier, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.InitiateAuthorizationCodeFlowAsync(ProtectedResourceMetadata protectedResourceMetadata, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)         at ModelContextProtocol.Authentication.ClientOAuthProvider.PerformOAuthAuthorizationAsync(HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.ClientOAuthProvider.HandleUnauthorizedResponseAsync(String scheme, HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.AuthenticatingMcpHttpClient.HandleUnauthorizedResponseAsync(HttpRequestMessage originalRequest, JsonRpcMessage originalJsonRpcMessage, HttpResponseMessage response, CancellationToken cancellationToken)
             at ModelContextProtocol.Authentication.AuthenticatingMcpHttpClient.SendAsync(HttpRequestMessage request, JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.SseClientSessionTransport.ReceiveMessagesAsync(CancellationToken cancellationToken)
             at ModelContextProtocol.Client.SseClientSessionTransport.CloseAsync()
             at ModelContextProtocol.Client.SseClientSessionTransport.ConnectAsync(CancellationToken cancellationToken)
             at ModelContextProtocol.Client.AutoDetectingClientSessionTransport.InitializeSseTransportAsync(JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.AutoDetectingClientSessionTransport.InitializeSseTransportAsync(JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.AutoDetectingClientSessionTransport.InitializeAsync(JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.AutoDetectingClientSessionTransport.InitializeAsync(JsonRpcMessage message, CancellationToken cancellationToken)
             at ModelContextProtocol.McpSession.SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken)
             at ModelContextProtocol.McpEndpointExtensions.SendRequestAsync[TParameters,TResult](IMcpEndpoint endpoint, String method, TParameters parameters, JsonTypeInfo`1 parametersTypeInfo, JsonTypeInfo`1 resultTypeInfo, RequestId requestId, CancellationToken cancellationToken)
             at ModelContextProtocol.Client.McpClient.ConnectAsync(CancellationToken cancellationToken)
    
  3. added
    ready for workHas enough information to start
    and removed on Feb 24, 2026
  4. added this to the milestone on Feb 24, 2026
  5. added a commit that references this issue on Feb 28, 2026
    ac75230
  6. naqvir90 commented on Mar 23, 2026

    @naqvir90

    guys, this generated resource query string with entra is blocking us from using it as an IDP for copilot. Can someone please provide a workaround for this? As mentioned previously even when i remove the resource QS manually , the mcp sdk client receives the same error. Is the current state of the mcp sdk not compatible with using entra as an IDP? If it is, can someone please point us in the right direction?

  7. erwinkramer commented on Mar 23, 2026

    @erwinkramer

    @naqvir90 I'm also waiting for this issue. But I have a working setup here - https://github.com/erwinkramer/bank-api, but it's not using the SDK and is just based on an OpenAPI to MCP generator (with Entra ID auth), maybe that works for you too. This is fully tested with the GitHub Copilot client.

  8. removed this from the milestone on May 7, 2026
  9. added
    bugSomething isn't working
    P2Moderate issues, valuable feature requests
    on Jul 19, 2026
  10. added this to the 2.x milestone on Jul 19, 2026
  11. jeffhandley commented on Jul 19, 2026

    @jeffhandley
    Contributor

    Context from #741 (closed as a duplicate of this issue): authenticating an MCP client against Microsoft Entra ID fails with AADSTS901002: The 'resource' request parameter is not supported. It occurs on both the authorization-code request and the token request. The reporter confirmed that removing the resource parameter (patching ClientOAuthProvider.cs locally) resolves it -- so the SDK unconditionally sending resource in its OAuth requests is the blocker for using Entra ID as an authorization server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues, valuable feature requestsarea-authbugSomething isn't workingready for workHas enough information to start

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions