Closed blowdart closed 6 years ago
"Bearer" as specified in the request headers is independent of "Bearer" used internally to identify different authentication providers. Start by calling the overload of AddJwtBearer that lets you specify a provider name: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerExtensions.cs#L20
The next challenge you'll run into is deciding which to run on a given request. The Microsoft.Owin stack would try every Jwt provider on every request, but in AspNetCore the design is based around only running the one you need to. How do you distinguish which credentials you expect for a request?
@Tratcher When you say provider name I assume you mean authentication scheme. I've tried calling the overload you mentioned before but since each config option ties to the same scheme it fails with the same error: System.InvalidOperationException: 'Scheme already exists: Bearer' e.g.
var authenticationConfiguration = this.configuration.GetSection("AuthenticationConfiguration").Get<AuthenticationConfiguration>();
services.AddAuthentication()
.AddJwtBearer(
"Bearer",
options =>
{
options.Authority = "https://login.microsoftonline.de/common";
options.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = authenticationConfiguration.RequireExpirationTime,
ValidateLifetime = authenticationConfiguration.ValidateLifetime,
ValidateIssuer = authenticationConfiguration.ValidateIssuer,
ValidateAudience = authenticationConfiguration.ValidateAudience,
ValidAudiences = authenticationConfiguration.ValidAudiences
};
})
.AddJwtBearer(
"Bearer",
options =>
{
options.Authority = "https://login.microsoftonline.com/common";
options.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = authenticationConfiguration.RequireExpirationTime,
ValidateLifetime = authenticationConfiguration.ValidateLifetime,
ValidateIssuer = authenticationConfiguration.ValidateIssuer,
ValidateAudience = authenticationConfiguration.ValidateAudience,
ValidAudiences = authenticationConfiguration.ValidAudiences
};
});
The only way I could get this to work was to provide different schemes.
For our service we support both app and app+user token requests and as a multi-tenant application supporting multiple clouds our consumers can provide us with their bearer auth credentials and we only expect this layer to do the basic authN while authZ is done separately. We support any credentials that meet any of our authentication configurations to identify the caller but we may block requests who aren't authorized to perform certain actions and this worked well for us using the owin stack as you mentioned.
To your question we don't really distinguish between credentials we expect for a request. We just clients to come in with a token where we can validate with any one of our configured authorities whether it is the Azure global cloud, Azure China cloud, the Azure German cloud, or the Azure US government cloud.
.AddJwtBearer(
"Bearer",
This does not change the expected header value, it's only an internal unique identifier. Change it to "BearerDe" and "BearerCom" for example.
As for authenticating any given provider, you'd have to have an additional middleware that looped through the available providers and called Authenticate for each of them, or at least until it got a hit.
@Tratcher If I understand what you are saying correctly, out of box there isn't support to do this so I will need to introduce custom logic to do what I want?
To authenticate multiple providers? No, but it's a common question and there are several examples in this issue tracker. I'll see if I can find one.
@Tratcher Thanks. It would be great if you can share 👍
@Tratcher here a use case where you could have 2 providers relying in fact on the same Azure AD B2C https://stackoverflow.com/questions/35072371/headless-authentication-azure-ad-b2c/49036907#49036907
Comments on closed issues are not tracked, please open a new issue with the details for your scenario.
From @jimmy3912msncom on August 24, 2018 16:8
We recently started moving our applications from our legacy ASP.NET owin web hosted web API to ASP.NET Core. Our application supported JWT Bearer authentication for various clouds: the Azure global cloud, Azure China cloud, the Azure German cloud, and the Azure US government cloud.
The way we did this was having multiple configurations for each cloud and calling foreach (var tokenConfiguration in this.config.TokenAuthenticationConfigurations) { var aadTokenOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions() { Tenant = tokenConfiguration.TenantId, MetadataAddress = $"{tokenConfiguration.OpenIdConnectDiscoveryMetadataEndpoint}/{tokenConfiguration.TenantId}/{tokenConfiguration.FederationMetadataUrlSuffix}", TokenHandler = new JwtSecurityTokenHandler(), TokenValidationParameters = new TokenValidationParameters() { ValidAudiences = tokenConfiguration.AadTokenValidAudiences, ValidateIssuer = false, // can't set to true due to multi-tenant access enabled apps and different metadata address for each ValidateAudience = tokenConfiguration.ValidateAudience, ValidateIssuerSigningKey = tokenConfiguration.ValidateIssuerSigningKey, ValidateLifetime = tokenConfiguration.ValidateLifetime } }; app.UseWindowsAzureActiveDirectoryBearerAuthentication(aadTokenOptions); }
By doing this we could have the same authentication scheme support any of our users from any cloud since the authority to use when making open id connect metadata discovery calls were different for each cloud (and the corresponding metadata itself was different e.g. signing keys, etc...).
This way all of our callers can use the same authorization bearer scheme when calling our APIs, regardless of cloud.
With ASP.NET Core 2.1, I tried to do this as well as below:
var authenticationConfigurations = this.configuration.GetSection("AuthenticationConfigurations").Get<IEnumerable>();
var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
foreach (var authenticationConfiguration in authenticationConfigurations)
{
authenticationBuilder.AddJwtBearer(options =>
{
options.Authority = authenticationConfiguration.Authority;
options.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = authenticationConfiguration.RequireExpirationTime,
ValidateLifetime = authenticationConfiguration.ValidateLifetime,
ValidateIssuer = authenticationConfiguration.ValidateIssuer,
ValidateAudience = authenticationConfiguration.ValidateAudience,
ValidAudiences = authenticationConfiguration.ValidAudiences
};
});
}
but it fails with: System.InvalidOperationException: 'Scheme already exists: Bearer' at Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(String name, Actionb0(AuthenticationOptions o)
at Microsoft.Extensions.Options.ConfigureNamedOptionsb0()
at System.Lazyb 0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass4_0.b__0(RequestDelegate next)
at Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.StartAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token, String shutdownMessage)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
1 configureBuilder) at Microsoft.AspNetCore.Authentication.AuthenticationBuilder.<>c__DisplayClass4_0
2.1.Configure(String name, TOptions options) at Microsoft.Extensions.Options.OptionsFactory
1.Create(String name) at Microsoft.Extensions.Options.OptionsManager`1.<>cDisplayClass5_0.1.ViaFactory(LazyThreadSafetyMode mode) at System.Lazy
1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) at System.Lazy1.CreateValue() at Microsoft.Extensions.Options.OptionsCache
1.GetOrAdd(String name, Func1 createOptions) at Microsoft.Extensions.Options.OptionsManager
1.Get(String name) at Microsoft.Extensions.Options.OptionsManager1.get_Value() at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions
1 options, IDictionary2 schemes) at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions
1 options) --- End of stack trace from previous location where exception was thrown --- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(IServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor
2.VisitCallSite(IServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.Is there a way to enable this? I want to avoid having to make our users change call patterns if it is unnecessary to.
Microsoft.NETCore.App version 2.1.0 Microsoft.AspNetCore.All 2.1.2
Copied from original issue: aspnet/Home#3455