AzureAD / microsoft-authentication-library-for-dotnet

Microsoft Authentication Library (MSAL) for .NET
https://aka.ms/msal-net
MIT License
1.36k stars 330 forks source link

Regression in Microsoft.Windows.Client 4.61.1? - System.TypeLoadException #4789

Closed DerAlbertCom closed 3 weeks ago

DerAlbertCom commented 1 month ago

Library version used

4.61.1

.NET version

.NET 8

Scenario

ConfidentialClient - service to service (AcquireTokenForClient)

Is this a new or an existing app?

The app is in production, and I have upgraded to a new version of MSAL

Issue description and reproduction steps

We had an auto-upgrade of Microsoft.Identity.Client from 4.61.0 to 4.61.1 with renovate. Suddenly, our API Token Acquisition did not work anymore. We get the Exception.

System.TypeLoadException: Could not load type 'Microsoft.Identity.Client.Extensibility.AcquireTokenForClientBuilderExtensions' from assembly 'Microsoft.Identity.Client, Version=4.61.1.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae'.

at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions) at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions) at Microsoft.Identity.Web.TokenAcquisition.GetAccessTokenForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions) at Carmen.Position.Infrastructure.ExternalServices.DownstreamApiTokenAcquisitionHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /home/vsts/work/1/s/backend/src/Infrastructure/ExternalServices/DownstreamApiTokenAcquisitionHandler.cs:line 10 at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.gCore|5_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken) at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at Carmen.Position.Infrastructure.EmtInterface.Repository.EmtAuctionResultServiceClient.<>cDisplayClass2_0.<b__0>d.MoveNext() in /home/vsts/work/1/s/backend/src/Infrastructure/EmtInterface/Repository/EmtAuctionResultServiceClient.cs:line 27

Relevant code snippets

Our Token Aquisition
#
public class DownstreamApiTokenAcquisitionHandler(ITokenAcquisition acquisition, string appScope) : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var accessToken = await acquisition.GetAccessTokenForAppAsync(appScope);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        return await base.SendAsync(request, cancellationToken);
    }
}

Handler registration

    public static IHttpClientBuilder AddDownstreamApiTokenAcquisitionHandler(this IHttpClientBuilder builder, Func<IServiceProvider, string> appScopeFunc)
    {
        return builder.AddHttpMessageHandler(
            provider =>
            {
                var appScope = appScopeFunc(provider);
                return ActivatorUtilities.CreateInstance<DownstreamApiTokenAcquisitionHandler>(provider, appScope);
            });
    }

Together with HttpClientFactory

        services.AddHttpClient<TInterface, TClient>(
                (serviceProvider, client) =>
                {
                    var optionsFactory = serviceProvider.GetRequiredService<IOptionsMonitor<DownstreamApiOptions>>();
                    var options = optionsFactory.Get(serviceName);
                    client.BaseAddress = new Uri(options.BaseUrl!);
                })
            .AddDownstreamApiTokenAcquisitionHandler(
                provider =>
                {
                    var optionsFactory = provider.GetRequiredService<IOptionsMonitor<DownstreamApiOptions>>();
                    var options = optionsFactory.Get(serviceName);
                    var appScope = options.Scopes?.SingleOrDefault(s => s.StartsWith("api://", StringComparison.Ordinal));
                    return appScope ?? throw new InvalidOperationException($"Missing a api//* Scope for {serviceName}");
                })
            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { UseProxy = false });


### Expected behavior

Just work as in 4.61.0.... or find the problem 

### Identity provider

Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)

### Regression

4.61.0

### Solution and workarounds

Downgrade to 4.61.0 resolved the problem.
bgavrilMS commented 1 month ago

Good catch. It's because of this https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/4758/files

Strictly speaking it's not a build breaking change, but since MSAL is 2 layers down, a break occurs. We will fix this. Please stay on 4.61.0 for now.

mancyc commented 1 month ago

@bgavrilMS is there an ETA for the release with the fix? We're hitting this too.

bgavrilMS commented 4 weeks ago

@mancyc @DerAlbertCom - if you remove the explicit dependnecy on MSAL, do you still have problems?

mancyc commented 4 weeks ago

Nope, <4.61.1 works fine, so this is not urgent for me