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! 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);