Follow this article
Implementing mTLS Between Two Apps Using ASP.NET
Guidance is requested on implementing mTLS between two applications using ASP.NET MVC in C#. or any .net code. The setup will involve a handshake between the server app and client app, developed in a localhost environment, utilizing OpenSSL or another tool for self-signed certificates.Once the proof of concept is successful, the intention is to migrate the solution to Azure App Services. Thank you.
ASP.NET Core
ASP.NET
ASP.NET API
-
AgaveJoe • 29,441 Reputation points
2025-01-07T13:55:58.1033333+00:00 Guidance is requested on implementing mTLS between two applications using ASP.NET MVC in C#
Have you tried Google?
-
XuDong Peng-MSFT • 11,176 Reputation points • Microsoft Vendor
2025-01-08T08:20:10.6966667+00:00 Hi @Abdul Baba Syed,
Guidance is requested on implementing mTLS between two applications using ASP.NET MVC in C#. or any .net code.
It seems that you want to configure certificate authentication in your code, maybe you can refer to this official document, it includes sample steps such as code (.NET Core), creating certificates, etc.: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-9.0
In addition, whether your requirements are restricted to a specific environment, .NET framework or .NET Core?
-
Abdul Baba Syed • 21 Reputation points
2025-01-08T16:01:29.5233333+00:00 Thanks, I got a confirmation, that POC should be done in azure app service, appreciate some guidance or code for the server and client (both) using mtls cert for app service with self signed certs. Appreciate your help
Sign in to comment
1 answer
Sort by: Most helpful
-
Bruce (SqlWork.com) • 69,976 Reputation points
2025-01-08T16:22:28.95+00:00 -
Abdul Baba Syed • 21 Reputation points
2025-01-09T05:19:44.3366667+00:00 Thanks
how to validate at the server side code, not just a header? and how does the client carry certificate and present to the server and how to validate it. (Both side process im looking) Thanks
-
Bruce (SqlWork.com) • 69,976 Reputation points
2025-01-09T17:05:27.4733333+00:00 it depends on how the server code is hosted.
if the server code is hosted by IIS then IIS manages the all connection and certificate validation. see:
if using Kestrel then something like:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-9.0
from the client:
-
Abdul Baba Syed • 21 Reputation points
2025-01-11T06:27:10.23+00:00 Thanks Bruce
Below is the client Side code:
using System;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace ClientCertificateSender
{
class Program { static async Task Main(string[] args) { // Path to your client certificate (PFX file) and password string certPath = "path_to_your_client_certificate.pfx"; // e.g., "C:\\certs\\clientcert.pfx" string certPassword = "your_cert_password"; // Load the client certificate X509Certificate2 clientCert = new X509Certificate2(certPath, certPassword); // Create an HttpClientHandler and add the client certificate HttpClientHandler handler = new HttpClientHandler(); handler.ClientCertificates.Add(clientCert); // Allow self-signed certificates for testing (optional) handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; using (HttpClient client = new HttpClient(handler)) { // The server URL (replace with the actual URL of your server) string serverUrl = "https://yourserver.com/Cert"; try { // Send a GET request to the server HttpResponseMessage response = await client.GetAsync(serverUrl); // Read and display the response string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Response: {responseBody}"); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } } }
}
But at the client side, the client is loading the certificate from the path given above.
Im confused, do we send the client the .pfx cert or a private key? is the above code is valid?
As the azure app service (Server side) will check this cert and currently we are in multi-tenant app vm, does not have public key/pfx at the server side to validate. Only way is to check whether at the server side in app is the header as the value
// Configure the application to client certificate forwarded the frontend load balancer services.AddCertificateForwarding(options => { options.CertificateHeader = "X-ARR-ClientCert"; });
and extract the client thumprint validate from the Json/list and allow the app to access it.. appreciate your guidance, if any code available both for client and server based on the above requirement.. Thanks
-
Bruce (SqlWork.com) • 69,976 Reputation points
2025-01-11T19:14:25.48+00:00 The certificate must be installed on the server. It used to verify the ssl connection. Typically the certificate is generated and installed on the server, then sent to clients to install. the certificate is verified by the server ssl endpoint. The ssl connection will fail if a valid certificate is not supplied.
The azure load balancer does not support ssl certificates, so cannot be used. In azure you use application gateways that support ssl certificates. the gateway has support for the certificate validation. The gateway can pass the certificate to server as forward header, so the server can know the client using the connection.
normally the .pfx which has the certificate inside is password encrypted, so a password is required to read the certificate value. The private key (only know by the server) is used by the server validating the certificate
-
Abdul Baba Syed • 21 Reputation points
2025-01-17T14:50:35.5066667+00:00 Thanks Bruce,
Here is the Server side validation code hosted in Azure app service
using System;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Mvc;
namespace mtlsserver1.Controllers
{
public class MTLSController : Controller { private readonly ILogger<MTLSController> _logger; public MTLSController(ILogger<MTLSController> logger) { _logger = logger; } [HttpGet] [Route("api/validate")] public IActionResult ValidateClientCertificate() { foreach (var header in Request.Headers) { _logger.LogInformation("Header: {Key} = {Value}", header.Key, header.Value); } string clientCertBase64 = Request.Headers["X-ARR-ClientCert"]; if (string.IsNullOrEmpty(clientCertBase64)) { _logger.LogWarning("No client certificate provided in the request."); return StatusCode(403, "Client certificate is required."); } try { byte[] clientCertBytes = Convert.FromBase64String(clientCertBase64); var clientCertificate = new X509Certificate2(clientCertBytes); string clientThumbprint = clientCertificate.Thumbprint; var certProperties = new { Subject = clientCertificate.Subject, Issuer = clientCertificate.Issuer, Thumbprint = clientCertificate.Thumbprint, ExpirationDate = clientCertificate.NotAfter, EffectiveDate = clientCertificate.NotBefore, SerialNumber = clientCertificate.SerialNumber }; _logger.LogInformation("Client Certificate Properties: {@CertProperties}", certProperties); // Log or return the properties for display //Console.WriteLine($"Client Certificate Properties:\n" + // $" Subject: {certProperties.Subject}\n" + // $" Issuer: {certProperties.Issuer}\n" + // $" Thumbprint: {certProperties.Thumbprint}\n" + // $" Effective Date: {certProperties.EffectiveDate}\n" + // $" Expiration Date: {certProperties.ExpirationDate}\n" + // $" Serial Number: {certProperties.SerialNumber}"); if (IsTrustedClient(clientThumbprint)) { _logger.LogInformation("Client certificate is valid."); return Ok(new { message = "Client certificate is valid." }); } else { _logger.LogWarning("Invalid client certificate. Thumbprint: {Thumbprint}", clientThumbprint); return StatusCode(403, "Invalid client certificate."); } } catch (Exception ex) { _logger.LogError(ex, "Error processing client certificate."); return StatusCode(500, $"Error processing client certificate: {ex.Message}"); } } private bool IsTrustedClient(string thumbprint) { _logger.LogInformation("Checking if thumbprint {Thumbprint} is in the trusted list.", thumbprint); var trustedThumbprints = new[] { "5f4xxxxxxxx33010328aa30108dxxxxxxx", "46sxxxxxxxxx9729CE24AE398Cxxxxx", "3F6Dxxxxxxxxx4756D9C71458BExxxxxxx" }; return Array.Exists(trustedThumbprints, t => t.Equals(thumbprint, StringComparison.OrdinalIgnoreCase)); } }
}
and Client side validation code hosted in Azure app service, certificate is being fetched from azure
-----------------------------------------------------------public async Task<IActionResult> Index()
{
try { // Define the thumbprint of the client certificate string certThumbprint = "4627A80F95745C9729CE24AE398C34B658537BBD"; // Fetch the client certificate from the local machine store X509Certificate2 clientCert = GetCertificateByThumbprint(certThumbprint); if (clientCert == null) { //ViewBag.Message = "Client certificate not found."; //return View(); return NotFound("Client certificate not found."); } // return View(clientCert.FriendlyName); // Display some certificate properties var certProperties = new { Subject = clientCert.Subject, Issuer = clientCert.Issuer, Thumbprint = clientCert.Thumbprint, ExpirationDate = clientCert.NotAfter, EffectiveDate = clientCert.NotBefore, SerialNumber = clientCert.SerialNumber }; //ViewBag.CertProperties = certProperties; // Log or return the properties for display //return Ok($"Client Certificate Properties:\n" + // $" Subject: {certProperties.Subject}\n" + // $" Issuer: {certProperties.Issuer}\n" + // $" Thumbprint: {certProperties.Thumbprint}\n" + // $" Effective Date: {certProperties.EffectiveDate}\n" + // $" Expiration Date: {certProperties.ExpirationDate}\n" + // $" Serial Number: {certProperties.SerialNumber}"); // Create an HttpClientHandler and attach the client certificate var handler = new HttpClientHandler(); handler.ClientCertificates.Add(clientCert); handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; // Create an HttpClient instance using (var client = new HttpClient(handler)) { // Send the request to the MTLS-enabled server var response = await client.GetAsync("https://mtlsserver-xxxxxxxxxxx.azurewebsites.net/api/validate"); if (response.IsSuccessStatusCode) { var responseBody = await response.Content.ReadAsStringAsync(); // return Ok($"Server response: {responseBody}"); return Ok(new { ServerResponse = $"Server response: {responseBody}", CertificateProperties = certProperties }); //ViewBag.ServerResponse = "Server response: " + responseBody; } else { return StatusCode((int)response.StatusCode, "Request failed."); } } } catch (Exception ex) { return StatusCode(500, $"Error: {ex.Message}"); } //return View();
}
private X509Certificate2 GetCertificateByThumbprint(string thumbprint)
{
// Open the local machine certificate store using (var store = new X509Store(StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadOnly); // Find the certificate by thumbprint var certCollection = store.Certificates.Find( X509FindType.FindByThumbprint, thumbprint, validOnly: false); // Return the first certificate found return certCollection.Count > 0 ? certCollection[0] : null; }
}
as Im extracting the header from the client certificate in the server side and validating with Thumbprint, if thumbprint is matches, im going to allow.. All works well, but when i see in the logs kudu i can see this error, can i ignore this?
Pls suggest. after this I can close out the thread, your help is appreciated2025-01-16 15:32:25.378 +00:00 [Trace] Microsoft.AspNetCore.HttpsPolicy.HstsMiddleware: Adding HSTS header to response. 2025-01-16 15:32:25.392 +00:00 [Warning] Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler: Certificate validation failed, subject was CN=clientmtls-f9bsckaye2aqahda.canadacentral-01.azurewebsites.net, O=Internet Widgits Pty Ltd, S=Some-State, C=AU. PartialChain A certificate chain could not be built to a trusted root authority., RevocationStatusUnknown The revocation function was unable to check revocation for the certificate., OfflineRevocation The revocation function was unable to check revocation because the revocation server was offline. 2025-01-16 15:32:25.392 +00:00 [Information] Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationHandler: Certificate was not authenticated. Failure message: Invalid client certificate.
Sign in to comment -