Combine Azure-AD with local user database in dotnet core 2 web-api

旧街凉风 提交于 2021-02-10 18:51:41

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!