Implement two authentication options (Token and Certificate) in ASP Net Core

Viewed 2613

[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://docs.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.

1 Answers

Hope what follows will help someone! I eventually found this link : https://docs.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;
Related