[Target netcoreapp3.1]
Hi there! So I have this Web Api that is protected by a middleware of this form in my Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//other services configuration
services.AddProtectedWebApi(options => { /* config */};
//other services configuration
}
This verifies Jwt Tokens issued by Azure and grants access to the API; it works fine. At present, I have a front-end angular client website where a user signs in via Azure AD. Angular sends the token to my web API and everything works.
I would now like to use the same webapp to handle query requests from a user without credentials, but with a client certificate that would have been provided in advance. So basically, I'd like to authenticate on my Angular WebSite via Azure OR via a client cert. Angular would then follow up the information to my webapp, which would in turn authenticate the user with the appropriate method.
To be clear, I still want someone to be able to log in without a certificate by using his Azure account.
Is there a simple way to have two authentication options in this case without having to create a separate webapp? I read a bit there : https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-3.1#optional-client-certificates But it seems it'd only work on the preview of ASP.NET Core 5, which I can't use in my situation.
Hope what follows will help someone! I eventually found this link : https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-3.1
It explains how to implement multiple authorization policies that both have a chance to succeed. Below is the solution I found using IIS after a bit more research:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//other services configuration
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = true;
});
services.Configure<CertificateForwardingOptions>(options =>
{
options.CertificateHeader = {/*your header present in client request*/};
});
//other services configuration
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes =/*Whatever you need*/;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
if ({/*CertValidationClass*/}.ValidateCertificate(context.ClientCertificate))
{
context.Success();
}
else
{
context.Fail("invalid cert");
}
return Task.CompletedTask;
}
};
});
services.AddProtectedWebApi(options => { /* config */};
//other services configuration
}
{CertValidationClass} being a service or helper class custom made to verify all I have to verify to approve the certificate. Obviously you can add a lot more verifying and actions on your own to this template.
I already had app.UseAuthentication(); app.UseAuthorization();
in my middleware pipeline, no need to change that, but you do have to add app.UseCertificateForwarding();
before these two.
Now I just had to specify above the controller I wanted to protect that I wanted to use both Authorization methods, and just like that, if one fails, it falls back on the other and it works perfectly, I tested by making requests via Insomnia with/without tokens and with/without certficates.
MyApiController.cs
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class MyApiController
{
//Just add the schemes you want used here
private const string AuthSchemes =
CertificateAuthenticationDefaults.AuthenticationScheme; + "," +
JwtBearerDefaults.AuthenticationScheme;