Search code examples
c#.netasp.net-core.net-coredotnet-httpclient

Add client certificate to .NET Core HttpClient


I was playing around with .NET Core and building an API that utilizes payment APIs. There's a client certificate that needs to be added to the request for two-way SSL authentication. How can I achieve this in .NET Core using HttpClient?

I have looked at various articles and found that HttpClientHandler doesn't provide any option to add client certificates.


Solution

  • Make all configuration in Main() like this:

    public static void Main(string[] args)
    {
        var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
        var logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
        string env="", sbj="", crtf = "";
    
        try
        {
            var whb = WebHost.CreateDefaultBuilder(args).UseContentRoot(Directory.GetCurrentDirectory());
    
            var environment = env = whb.GetSetting("environment");
            var subjectName = sbj = CertificateHelper.GetCertificateSubjectNameBasedOnEnvironment(environment);
            var certificate = CertificateHelper.GetServiceCertificate(subjectName);
    
            crtf = certificate != null ? certificate.Subject : "It will after the certification";
    
            if (certificate == null) // present apies even without server certificate but dont give permission on authorization
            {
                var host = whb
                    .ConfigureKestrel(_ => { })
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseIISIntegration()
                    .UseStartup<Startup>()
                    .UseConfiguration(configuration)
                    .UseSerilog((context, config) =>
                    {
                        config.ReadFrom.Configuration(context.Configuration);
                    })
                    .Build();
                host.Run();
            }
            else
            {
                var host = whb
                    .ConfigureKestrel(options =>
                    {
                        options.Listen(new IPEndPoint(IPAddress.Loopback, 443), listenOptions =>
                        {
                            var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions()
                            {
                                ClientCertificateMode = ClientCertificateMode.AllowCertificate,
                                SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
                                ServerCertificate = certificate
                            };
                            listenOptions.UseHttps(httpsConnectionAdapterOptions);
                        });
                    })
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseIISIntegration()
                    .UseUrls("https://*:443")
                    .UseStartup<Startup>()
                    .UseConfiguration(configuration)
                    .UseSerilog((context, config) =>
                    {
                        config.ReadFrom.Configuration(context.Configuration);
                    })
                    .Build();
                host.Run();
            }
    
            Log.Logger.Information("Information: Environment = " + env +
                " Subject = " + sbj +
                " Certificate Subject = " + crtf);
        }
        catch(Exception ex)
        {
            Log.Logger.Error("Main handled an exception: Environment = " + env +
                " Subject = " + sbj +
                " Certificate Subject = " + crtf +
                " Exception Detail = " + ex.Message);
        }
    }
    

    Configure file startup.cs like this:

    #region 2way SSL settings
    services.AddMvc();
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCertificateAuthentication(certOptions =>
    {
        var certificateAndRoles = new List<CertficateAuthenticationOptions.CertificateAndRoles>();
        Configuration.GetSection("AuthorizedCertficatesAndRoles:CertificateAndRoles").Bind(certificateAndRoles);
        certOptions.CertificatesAndRoles = certificateAndRoles.ToArray();
    });
    
    services.AddAuthorization(options =>
    {
        options.AddPolicy("CanAccessAdminMethods", policy => policy.RequireRole("Admin"));
        options.AddPolicy("CanAccessUserMethods", policy => policy.RequireRole("User"));
    });
    #endregion
    

    The certificate helper

    public class CertificateHelper
    {
        protected internal static X509Certificate2 GetServiceCertificate(string subjectName)
        {
            using (var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
            {
                certStore.Open(OpenFlags.ReadOnly);
                var certCollection = certStore.Certificates.Find(
                                           X509FindType.FindBySubjectDistinguishedName, subjectName, true);
                X509Certificate2 certificate = null;
                if (certCollection.Count > 0)
                {
                    certificate = certCollection[0];
                }
                return certificate;
            }
        }
    
        protected internal static string GetCertificateSubjectNameBasedOnEnvironment(string environment)
        {
            var builder = new ConfigurationBuilder()
             .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile($"appsettings.{environment}.json", optional: false);
    
            var configuration = builder.Build();
            return configuration["ServerCertificateSubject"];
        }
    }