From e31076a9fc130b23b0e9a9ac5b3b5c86dad8c0e6 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Mon, 6 Jul 2026 12:09:47 -0700 Subject: [PATCH 1/2] Register IAuthorizationHeaderProvider2 in DI DefaultAuthorizationHeaderProvider implements both IAuthorizationHeaderProvider and IAuthorizationHeaderProvider2, but only v1 was registered - so resolving v2 directly returned nothing (internal callers only worked because they cast). Register v2 as the same instance (mirroring how ITokenAcquisitionInternal is exposed off ITokenAcquisition) in both the singleton and scoped paths, and remove it on the lifetime-mismatch re-registration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ServiceCollectionExtensions.cs | 10 ++++ .../ServiceCollectionExtensionsTests.cs | 55 ++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ServiceCollectionExtensions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ServiceCollectionExtensions.cs index 4fd3ff838..dcdd6f9bb 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ServiceCollectionExtensions.cs @@ -75,6 +75,7 @@ public static IServiceCollection AddTokenAcquisition( ServiceDescriptor? ccaProviderService = services.FirstOrDefault(s => s.ServiceType == typeof(IConfidentialClientApplicationProvider)); ServiceDescriptor? tokenAcquisitionhost = services.FirstOrDefault(s => s.ServiceType == typeof(ITokenAcquisitionHost)); ServiceDescriptor? authenticationHeaderCreator = services.FirstOrDefault(s => s.ServiceType == typeof(IAuthorizationHeaderProvider)); + ServiceDescriptor? authenticationHeaderCreator2 = services.FirstOrDefault(s => s.ServiceType == typeof(IAuthorizationHeaderProvider2)); ServiceDescriptor? tokenAcquirerFactory = services.FirstOrDefault(s => s.ServiceType == typeof(ITokenAcquirerFactory)); ServiceDescriptor? authSchemeInfoProvider = services.FirstOrDefault(s => s.ServiceType == typeof(Abstractions.IAuthenticationSchemeInformationProvider)); ServiceDescriptor? credentialsProviderService = services.FirstOrDefault(s => s.ServiceType == typeof(ICredentialsProvider)); @@ -91,6 +92,11 @@ public static IServiceCollection AddTokenAcquisition( services.Remove(authenticationHeaderCreator); services.Remove(authSchemeInfoProvider); + if (authenticationHeaderCreator2 != null) + { + services.Remove(authenticationHeaderCreator2); + } + if (ccaProviderService != null) { services.Remove(ccaProviderService); @@ -148,6 +154,8 @@ public static IServiceCollection AddTokenAcquisition( services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); + // Expose the same instance as IAuthorizationHeaderProvider2 so it can be resolved directly. + services.AddSingleton(s => (IAuthorizationHeaderProvider2)s.GetRequiredService()); if (!HasImplementationType(services, typeof(CredentialsProvider))) { services.TryAddSingleton(); @@ -183,6 +191,8 @@ public static IServiceCollection AddTokenAcquisition( services.AddScoped(sp => sp.GetRequiredService()); services.AddScoped(); + // Expose the same instance as IAuthorizationHeaderProvider2 so it can be resolved directly. + services.AddScoped(s => (IAuthorizationHeaderProvider2)s.GetRequiredService()); if (!HasImplementationType(services, typeof(CredentialsProvider))) { services.TryAddScoped(); diff --git a/tests/Microsoft.Identity.Web.Test/ServiceCollectionExtensionsTests.cs b/tests/Microsoft.Identity.Web.Test/ServiceCollectionExtensionsTests.cs index 4f4b844af..dce7f6c56 100644 --- a/tests/Microsoft.Identity.Web.Test/ServiceCollectionExtensionsTests.cs +++ b/tests/Microsoft.Identity.Web.Test/ServiceCollectionExtensionsTests.cs @@ -74,6 +74,14 @@ public void AddTokenAcquisition_Sdk_AddsWithCorrectLifetime() Assert.Null(actual.ImplementationFactory); }, actual => + { + Assert.Equal(ServiceLifetime.Scoped, actual.Lifetime); + Assert.Equal(typeof(IAuthorizationHeaderProvider2), actual.ServiceType); + Assert.Null(actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.NotNull(actual.ImplementationFactory); + }, + actual => { Assert.Equal(typeof(ICredentialsLoader), actual.ServiceType); Assert.Equal(typeof(DefaultCertificateLoader), actual.ImplementationType); @@ -155,7 +163,7 @@ public void AddTokenAcquisition_Sdk_SupportsKeyedServices() services.AddTokenAcquisition(); // Verify the number of services added by AddTokenAcquisition (ignoring the service we added here). - Assert.Equal(14, services.Count(t => t.ServiceType != typeof(ServiceCollectionExtensionsTests))); + Assert.Equal(15, services.Count(t => t.ServiceType != typeof(ServiceCollectionExtensionsTests))); } #endif @@ -210,6 +218,43 @@ public void AddTokenAcquisition_ResolvesIConfidentialClientApplicationProviderTo Assert.IsAssignableFrom(confidentialClientApplicationProvider); } + [Fact] + public void AddTokenAcquisition_RegistersIAuthorizationHeaderProvider2() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddTokenAcquisition(); + + // Assert + ServiceDescriptor serviceDescriptor = Assert.Single(services, s => s.ServiceType == typeof(IAuthorizationHeaderProvider2)); + Assert.Equal(ServiceLifetime.Scoped, serviceDescriptor.Lifetime); + Assert.Null(serviceDescriptor.ImplementationType); + Assert.Null(serviceDescriptor.ImplementationInstance); + Assert.NotNull(serviceDescriptor.ImplementationFactory); + } + + [Fact] + public void AddTokenAcquisition_ResolvesIAuthorizationHeaderProvider2ToSameInstanceAsV1() + { + // Arrange + var services = new ServiceCollection(); + services.AddTokenAcquisition(); + services.AddInMemoryTokenCaches(); + services.AddHttpClient(); + using ServiceProvider serviceProvider = services.BuildServiceProvider(); + using IServiceScope serviceScope = serviceProvider.CreateScope(); + + // Act + var headerProvider = serviceScope.ServiceProvider.GetRequiredService(); + var headerProvider2 = serviceScope.ServiceProvider.GetRequiredService(); + + // Assert + Assert.Same(headerProvider, headerProvider2); + Assert.IsType(headerProvider2); + } + [Fact] public void AddHttpContextAccessor_ThrowsWithoutServices() { @@ -277,6 +322,14 @@ public void AddTokenAcquisitionCalledTwice_RegistersTokenAcquisitionOnlyAsSingle Assert.Null(actual.ImplementationFactory); }, actual => + { + Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime); + Assert.Equal(typeof(IAuthorizationHeaderProvider2), actual.ServiceType); + Assert.Null(actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.NotNull(actual.ImplementationFactory); + }, + actual => { Assert.Equal(typeof(ICredentialsLoader), actual.ServiceType); Assert.Equal(typeof(DefaultCertificateLoader), actual.ImplementationType); From 2ef45707ba0456db7f3ba84ad88ffa4905de16e4 Mon Sep 17 00:00:00 2001 From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:52:19 -0700 Subject: [PATCH 2/2] Add GetAuthorizationHeaderProvider2() to OWIN controllers (#3928) * Register IAuthorizationHeaderProvider2 in DI DefaultAuthorizationHeaderProvider implements both IAuthorizationHeaderProvider and IAuthorizationHeaderProvider2, but only v1 was registered - so resolving v2 directly returned nothing (internal callers only worked because they cast). Register v2 as the same instance (mirroring how ITokenAcquisitionInternal is exposed off ITokenAcquisition) in both the singleton and scoped paths, and remove it on the lifetime-mismatch re-registration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add GetAuthorizationHeaderProvider2() to OWIN controllers OWIN controllers already get a one-liner for the v1 IAuthorizationHeaderProvider; this adds the same convenience for IAuthorizationHeaderProvider2 on both ApiController and ControllerBase, resolving it from the OWIN TokenAcquirerFactory service provider. Depends on IAuthorizationHeaderProvider2 being registered in DI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ApiControllerExtensions.cs | 14 ++++++++++++++ .../ControllerBaseExtensions.cs | 14 ++++++++++++++ .../PublicAPI/PublicAPI.Unshipped.txt | 2 ++ 3 files changed, 30 insertions(+) diff --git a/src/Microsoft.Identity.Web.OWIN/ApiControllerExtensions.cs b/src/Microsoft.Identity.Web.OWIN/ApiControllerExtensions.cs index d29024777..fa5dd9d53 100644 --- a/src/Microsoft.Identity.Web.OWIN/ApiControllerExtensions.cs +++ b/src/Microsoft.Identity.Web.OWIN/ApiControllerExtensions.cs @@ -42,6 +42,20 @@ public static IAuthorizationHeaderProvider GetAuthorizationHeaderProvider(this A return headerProvider; } + /// + /// Get the authorization header provider, exposing the richer surface. + /// + /// + public static IAuthorizationHeaderProvider2 GetAuthorizationHeaderProvider2(this ApiController _) + { + IAuthorizationHeaderProvider2? headerProvider = TokenAcquirerFactory.GetDefaultInstance().ServiceProvider?.GetService(typeof(IAuthorizationHeaderProvider2)) as IAuthorizationHeaderProvider2; + if (headerProvider == null) + { + throw new ConfigurationErrorsException("Cannot find IAuthorizationHeaderProvider2. Did you create an OwinTokenAcquirerFactory in Startup_Auth.cs?. See https://aka.ms/ms-id-web/owin. "); + } + return headerProvider; + } + /// /// Get the downstream API service from an ApiController. /// diff --git a/src/Microsoft.Identity.Web.OWIN/ControllerBaseExtensions.cs b/src/Microsoft.Identity.Web.OWIN/ControllerBaseExtensions.cs index f56727505..3a2940422 100644 --- a/src/Microsoft.Identity.Web.OWIN/ControllerBaseExtensions.cs +++ b/src/Microsoft.Identity.Web.OWIN/ControllerBaseExtensions.cs @@ -42,6 +42,20 @@ public static IAuthorizationHeaderProvider GetAuthorizationHeaderProvider(this C return headerProvider; } + /// + /// Get the authorization header provider, exposing the richer surface. + /// + /// + public static IAuthorizationHeaderProvider2 GetAuthorizationHeaderProvider2(this ControllerBase _) + { + IAuthorizationHeaderProvider2? headerProvider = TokenAcquirerFactory.GetDefaultInstance().ServiceProvider?.GetService(typeof(IAuthorizationHeaderProvider2)) as IAuthorizationHeaderProvider2; + if (headerProvider == null) + { + throw new ConfigurationErrorsException("Cannot find IAuthorizationHeaderProvider2. Did you create an OwinTokenAcquirerFactory in Startup_Auth.cs?. See https://aka.ms/ms-id-web/owin. "); + } + return headerProvider; + } + /// /// Get the downstream API service from an ApiController. /// diff --git a/src/Microsoft.Identity.Web.OWIN/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.OWIN/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c5811..52a4524b7 100644 --- a/src/Microsoft.Identity.Web.OWIN/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.OWIN/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +static Microsoft.Identity.Web.ApiControllerExtensions.GetAuthorizationHeaderProvider2(this System.Web.Http.ApiController! _) -> Microsoft.Identity.Abstractions.IAuthorizationHeaderProvider2! +static Microsoft.Identity.Web.ControllerBaseExtensions.GetAuthorizationHeaderProvider2(this System.Web.Mvc.ControllerBase! _) -> Microsoft.Identity.Abstractions.IAuthorizationHeaderProvider2!