AzureAD/microsoft-identity-web

.NET 8 WebApi throws InvalidOperationException when keyed Services are registered before MS Identity Services are registered

thuijer opened this issue · 4 comments

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

2.15.2

Web app

Not Applicable

Web API

Not Applicable

Token cache serialization

Not Applicable

Description

In an .NET 8 ASP.NET Core WebApi, when keyed services are being added to the DI container before calling AddAuthentication(builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")), the service throws an InvalidOperationException with message "Your service provider may not support keyed services" upon startup.

Reproduction steps

  1. Create a standard ASP.NET Core WebAPI with auth set to Microsoft Identity Platform
  2. Create an empty Interface ISomeInterface : interface ISomeInterface {}
  3. Create a class SomeConcreteClass that implements ISomeInterface: `class SomeConcreteClass: ISomeInterface {}
  4. Add builder.Services.AddKeyedSingleton<ISomeInterface, SomeConcreteClass>("A") *BEFORE* the call to builder.Services.AddAuthentication(builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))`
  5. Run the application and see that it crashes
  6. Move the line builder.Services.AddKeyedSingleton<ISomeInterface, SomeConcreteClass>("A") until after the builder.Services.AddAuthentication() call
  7. Run the application and see that it starts up as expected

Error message

InvalidOperationException with message "Your service provider may not support keyed services"

at Microsoft.Extensions.DependencyInjection.ServiceDescriptor.ThrowKeyedDescriptor() in //src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs:line 1061
at Microsoft.Extensions.DependencyInjection.ServiceDescriptor.get_ImplementationType() in /
/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs:line 164
at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilderExtensions.<>c.b__3_0(ServiceDescriptor s) in //src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs:line 151
at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable1 source, Func2 predicate, Boolean& found) in /
/src/libraries/System.Linq/src/System/Linq/First.cs:line 115
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable1 source, Func2 predicate) in //src/libraries/System.Linq/src/System/Linq/First.cs:line 49
at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilderExtensions.AddMicrosoftIdentityWebApiImplementation(AuthenticationBuilder builder, Action`1 configureJwtBearerOptions, String jwtBearerScheme, Boolean subscribeToJwtBearerMiddlewareDiagnosticsEvents) in /
/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs:line 151
at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilderExtensions.AddMicrosoftIdentityWebApi(AuthenticationBuilder builder, IConfigurationSection configurationSection, String jwtBearerScheme, Boolean subscribeToJwtBearerMiddlewareDiagnosticsEvents) in /_/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs:line 84
at Program.

$(String[] args) in C:\Demo\WebApplication2\Program.cs:line 12

Id Web logs

No response

Relevant code snippets

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// if this line is *BEFORE* the `AddAuthentication` call, the app crashes at startup
// if this line is *AFTER* the `AddAuthentication` call, the app starts up as expected
builder.Services.AddKeyedSingleton<IMyInterface, MyConcreteType>("A");

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
        .EnableTokenAcquisitionToCallDownstreamApi()
            .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
            .AddInMemoryTokenCaches();
builder.Services.AddAuthorization();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.Run();

internal interface IMyInterface
{
}

internal class MyConcreteType : IMyInterface
{
}

Regression

No response

Expected behavior

App starts up normally

amis92 commented

We've just hit this. Right now it prevents targeting net8.0 without workarounds, and worse - it's enough if a project references Microsoft.Extensions.DependencyInjection.Abstractions v8 (which is supported on net6 as well), so simply by a dependency update (not retargeting to new framework) it can crash.

Edit: I've raised dotnet/runtime#95789 since it seems to be a very problematic regression.

Any progress on a fix yet?

I've opened a fix PR: #2676

This should probably be released sooner rather than later...