问题
I am creating a .net-core2 web-api, which allows users from an Azure-AD to consume it. The API is multi-tenant, so users from multiple Azure-AD's should be able to authorize.
However, it is also possible to create an account for users who do not have a corporate Azure-AD account. These users are stored in a database (local users).
Because it is a web-api, I implemented a custom token provider, so that the local users can get a token to consume the protected web-api.
However, I cannot add two separate 'Bearer' authentications to the web-api:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options))
.AddJwtBearer(options => new JwtBearerOptions {
TokenValidationParameters = tokenValidationParameters
});
This throws an error:
System.InvalidOperationException: Scheme already exists: Bearer
Which I totally understand. But how I can implement both authentication mechanisms in parallel?
回答1:
You have to specify a different identifier. Both are using the "Bearer" identifier at the moment.
For example, you can specify a different one for JWT Bearer by:
.AddJwtBearer("CustomJwt", options => { });
This solves the issue with the identifier clash, but in order to support two authentication schemes in parallel, you will need to do additional modifications.
One way in 2.0 is something suggested by David Fowler: https://github.com/aspnet/Security/issues/1469
app.UseAuthentication();
app.Use(async (context, next) =>
{
// Write some code that determines the scheme based on the incoming request
var scheme = GetSchemeForRequest(context);
var result = await context.AuthenticateAsync(scheme);
if (result.Succeeded)
{
context.User = result.Principal;
}
await next();
});
In your case you could all the Bearer (Azure AD) scheme if there is no user on the context when you hit the middleware.
In ASP.NET Core 2.1 we will get "virtual authentication schemes", which allow this scenario in a more first-class way: https://github.com/aspnet/Security/pull/1550
回答2:
Thanks to juunas I found a working solution. What I did:
In Startup.cs ConfigureServices I added both authentication schemes:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options))
.AddJwtBearer("JWTBearer", options => {
options.TokenValidationParameters = tokenValidationParameters;
});
Then make sure in the Authorization you enable both schemes:
services.AddAuthorization(config => {
config.AddPolicy(PolicyNames.RequireKeyUser,
policy =>
{
policy.AddRequirements(new KeyUserRequirement());
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes("JWTBearer", JwtBearerDefaults.AuthenticationScheme);
});
});
And write some logic in Configure to determine the auth scheme on runtime:
app.Use(async (context, next) =>
{
// Write some code that determines the scheme based on the incoming request
var scheme = GetSchemeForRequest(context);
if (!String.IsNullOrEmpty(scheme)) {
var result = await context.AuthenticateAsync(scheme);
if (result.Succeeded)
{
context.User = result.Principal;
}
}
await next();
});
I decided to use an additional header 'Authorization-Type' to define my custom JWT authorization and use the default 'Bearer' prefix in the 'Authorization' header. So my GetSchemeForRequest function:
private string GetSchemeForRequest(HttpContext context)
{
var scheme = "";
try {
if (!String.IsNullOrEmpty(context.Request.Headers["Authorization"].ToString())) {
string authHeader = context.Request.Headers["Authorization-Type"].ToString();
if (authHeader == "JWT") {
scheme = "JWTBearer";
} else {
scheme = "Bearer";
}
}
}
catch (Exception ex) {
// Use your own logging mechanism
}
return scheme;
}
来源:https://stackoverflow.com/questions/49387682/combine-azure-ad-with-local-user-database-in-dotnet-core-2-web-api