microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

Calling Downstream Web API fails when using ADFS.

Open robv8r opened this issue 1 year ago • 12 comments

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

2.15.1

Web app

Not Applicable

Web API

Protected web APIs call downstream web APIs

Token cache serialization

In-memory caches

Description

When using EnableTokenAcquisitionToCallDownstreamApi with ADFS, an error occurs. (See Error Message section below).

An example application that reproduces this error can be found here.

The error is throw from this section of code in the BaseAbstractApplicationBuilder class.

The error occurs when attempting to call the downstream Web API.

Reproduction steps

  1. Clone https://github.com/robv8r/example-react-app
  2. Setup ADFS with appropriate settings
  3. Replace appsettings values with those from ADFS.

Error message

MSAL.NetCore.4.56.0.0.MsalClientException: 
	ErrorCode: tenant_override_non_aad
Microsoft.Identity.Client.MsalClientException: Cannot use WithTenantId() in the application builder, because the authority Adfs doesn't support it
   at Microsoft.Identity.Client.BaseAbstractApplicationBuilder`1.ResolveAuthority()
   at Microsoft.Identity.Client.AbstractApplicationBuilder`1.BuildConfiguration()
   at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.BuildConcrete()
   at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.Build()
   at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(MergedOptions mergedOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(MergedOptions mergedOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.DefaultAuthorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(IEnumerable`1 scopes, AuthorizationHeaderProviderOptions downstreamApiOptions, ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.DownstreamApi.CallApiInternalAsync(String serviceName, DownstreamApiOptions effectiveOptions, Boolean appToken, HttpContent content, ClaimsPrincipal user, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.DownstreamApi.CallApiForUserAsync[TOutput](String serviceName, Action`1 downstreamApiOptionsOverride, ClaimsPrincipal user, CancellationToken cancellationToken)
   at ExampleReactApp.Server.Controllers.DownstreamVersionController.Get(IDownstreamApi api, CancellationToken token) in C:\pj\git\example-react-app\src\server\Controllers\DownstreamVersionController.cs:line 14
   at lambda_method6(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: application/json, text/plain, */*
Connection: close
Host: localhost:5173
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Authorization: Bearer eyJ0...
Referer: https://localhost:5173/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-origin
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
DNT: 1
sec-ch-ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"

Id Web logs

No response

Relevant code snippets

[ApiController]
[Route("[controller]")]
public class DownstreamVersionController : ControllerBase
{
    [HttpGet]
    public async Task<DownstreamVersionInfo?> Get([FromServices] IDownstreamApi api, CancellationToken token)
    {
        return await api.CallApiForUserAsync<DownstreamVersionInfo>(
            "api",
            options =>
            {
                options.HttpMethod = HttpMethod.Get.Method;
                options.RelativePath = $"api/Version/Get";
            },
            cancellationToken: token)
            .ConfigureAwait(false);
    }
}

Regression

No response

Expected behavior

Because ADFS has a fixed tenant (adfs), the Microsoft.Identity library should not throw an exception due to WithTenantId() being unsupported.

robv8r avatar Oct 06 '23 21:10 robv8r

Please note that the example application targets net8.0. The issue described above is also present in net7.0 using VS 2022 v17.7.4.

The ADFS server is running on Windows Server 2019.

robv8r avatar Oct 06 '23 21:10 robv8r

@robv8r : did you try to specify the "Authority" directly in the configuration file?

jmprieur avatar Oct 06 '23 22:10 jmprieur

@jmprieur - I've tried many different configuration options. This morning, I attempted to specify the Authority in the appsettings.Development.json.

Old

"AzureAd": {
  "Instance": "https://login.companyname.com/",
  "ClientId": "Enter_the_Confidential_Client_Id_Here",
  "ClientSecret": "Enter_the_Confidential_Client_Secret_here",
  "TenantId": "adfs"
}

New

"AzureAd": {
  "Authority": "https://login.companyname.com/adfs",
  "ClientId": "Enter_the_Confidential_Client_Id_Here",
  "ClientSecret": "Enter_the_Confidential_Client_Secret_here"
}

This results in an invalid "v2.0" URL segment when attempting to retrieve metadata.

Message: IDX20803: Unable to obtain configuration from: 'https://login.companyname.com/adfs/v2.0/.well-known/openid-configuration'.

The original error is still present.

Microsoft.Identity.Web.TokenAcquisition[300]
      [MsIdWeb] An error occured during token acquisition: IDW10501: Exception acquiring token for a confidential client.
      MSAL.NetCore.4.56.0.0.MsalClientException:
        ErrorCode: tenant_override_non_aad
Microsoft.Identity.Client.MsalClientException: Cannot use WithTenantId() in the application builder, because the authority Adfs doesn't support it
         at Microsoft.Identity.Client.BaseAbstractApplicationBuilder`1.ResolveAuthority()
         at Microsoft.Identity.Client.AbstractApplicationBuilder`1.BuildConfiguration()
         at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.BuildConcrete()
         at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.Build()
         at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(MergedOptions mergedOptions)
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      MSAL.NetCore.4.56.0.0.MsalClientException:
        ErrorCode: tenant_override_non_aad
Microsoft.Identity.Client.MsalClientException: Cannot use WithTenantId() in the application builder, because the authority Adfs doesn't support it
         at Microsoft.Identity.Client.BaseAbstractApplicationBuilder`1.ResolveAuthority()
         at Microsoft.Identity.Client.AbstractApplicationBuilder`1.BuildConfiguration()
         at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.BuildConcrete()
         at Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.Build()
         at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(MergedOptions mergedOptions)
         at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(MergedOptions mergedOptions)
         at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
         at Microsoft.Identity.Web.DefaultAuthorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(IEnumerable`1 scopes, AuthorizationHeaderProviderOptions downstreamApiOptions, ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken)
         at Microsoft.Identity.Web.DownstreamApi.CallApiInternalAsync(String serviceName, DownstreamApiOptions effectiveOptions, Boolean appToken, HttpContent content, ClaimsPrincipal user, CancellationToken cancellationToken)
         at Microsoft.Identity.Web.DownstreamApi.CallApiForUserAsync[TOutput](String serviceName, Action`1 downstreamApiOptionsOverride, ClaimsPrincipal user, CancellationToken cancellationToken)
         at ExampleReactApp.Server.Controllers.DownstreamVersionController.Get(IDownstreamApi api, CancellationToken token) in C:\pj\git\example-react-app\src\server\Controllers\DownstreamVersionController.cs:line 14
         at lambda_method6(Closure, Object)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Please let me know if I've misunderstood your recommendation, or if there are other steps I should try.

Thank you, Rob

robv8r avatar Oct 09 '23 12:10 robv8r

I'm still unable to use EnableTokenAcquisitionToCallDownstreamApi with ADFS.

Is ADFS officially supported? If so, what's the best way to address this issue?

Thank you, Rob

robv8r avatar Oct 26 '23 16:10 robv8r

My previous comment may have been missed.

Does this repository officially support ADFS? If so, what's the best way to address this issue?

Thank you.

robv8r avatar Nov 29 '23 18:11 robv8r

Not officially, but we know some customers made it work. We have not prioritized work for Microsoft.Identity.Web to support ADFS (Microsoft.Identity.Web is about supporting Azure AD = Microsoft Entra ID, Azure AD B2C, and Microsoft Entra external IDs

jmprieur avatar Nov 29 '23 19:11 jmprieur

Per the documentation, ADFS is supported. Can you point me to documentation listing officially supported products?

[Edit]

The documentation includes instructions for connecting to ADFS directly from MSAL.

robv8r avatar Jan 01 '24 17:01 robv8r

@robv8r I came acorss exact same issue as you today and found a workaround. Just specify the Instance to "https://{your adfs service name}/adfs" in ConfidentialClientApplicationOptions . During debug, I can see the "TenantId" will be set to null which will not trigger the error anymore.

 services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
     .AddMicrosoftIdentityWebApp(options =>
     {
         Configuration.Bind("Adfs", options);
         options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false }; // This is to disable the tenant ID verification in ADFS login scenario
         options.SaveTokens = true; // This is to support sending "id_token_hint" in log out redirect to ADFS
     })
     .EnableTokenAcquisitionToCallDownstreamApi(
         options =>
         {
             Configuration.GetSection("TodoList:Scopes").Get<string[]>();
             options.Instance = "https://fs.contoso.com/adfs";
         }
     )
     .AddInMemoryTokenCaches();

JimmyLS avatar Feb 02 '24 11:02 JimmyLS

@jmprieur @jennyf19 - I think we should fix this in MSAL ? We can just have WithTenantId be no-op for ADFS. This avoids higher level APIs having to do "if ADFS ... do stuff".

bgavrilMS avatar Feb 05 '24 17:02 bgavrilMS

Sure @bgavrilMS Let's do that/

jmprieur avatar Feb 05 '24 20:02 jmprieur

@bgavrilMS do you have an ETA on when this will be fixed in MSAL?

jennyf19 avatar Mar 28 '24 02:03 jennyf19

Will be fixed by adopting MSAL 4.61.1+ (not yet released)

https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/4753

bgavrilMS avatar May 13 '24 11:05 bgavrilMS