I have an ASP.NET Core WebAPI (2.2) which uses two types of Authentication:
This is how I configure it within the Startup.cs:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddApiKeyAuthentication(options => Configuration.Bind("ApiKeyAuth", options));
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
My controllers are decorated with an Authorize attribute that includes both schemes. So I am able to invoke my web methods using either, a bearer token or by specifying an API key in the header - That works fine.
Now I added a custom middleware where I perform some tenant-specific checks. I registered the middleware inside the Configure method (after UseAuthentication):
// ....
app.UseAuthentication();
app.UseMiddleware<CustomMiddleware>()
Now if I invoke a REST method using bearer authentication, my CustomMiddleware gets called with an authenticated user - I can access the claims.
If I invoke the same REST method using my custom APIKey authentication, my CustomMiddleware gets called before my AuthenticationHandler.HandleAuthenticateAsync() method. The User is not authenticated - I can't access the claims (which I populate myself inside the HandleAuthenticateAsync method).
Why gets the middleware called before the authentication? How can I change that behavior so that the middleware gets called after the HandleAuthenticateAsync()?
You'll need to setup a forward default selector to make the API key authentication scheme the default when necessary.
Here is an example of that:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
jwtOptions.ForwardDefaultSelector = ctx =>
{
if (ctx.Request.Headers.TryGetValue("Api-Key", out var headerValues))
{
return "ApiKeyAuth";
}
return null;
};
})
.AddApiKeyAuthentication(options => Configuration.Bind("ApiKeyAuth", options));
I made some guesses to construct the selector, but basically it needs to return the name of another scheme if another should be used, and null otherwise.
In the example it checks if an "Api-Key" header is on the request, and if yes, returns "ApiKeyAuth" which is the API key scheme's name. Swap those to your values if they are different.
If in both schemes an Authorization: Bearer xyz header was used, you would need to inspect the header contents in the selector to make the decision.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With