7

Implement certificate authentication in ASP.NET Core for an Azure B2C API connec...

 2 years ago
source link: https://damienbod.com/2021/11/22/implement-certificate-authentication-in-asp-net-core-for-an-azure-b2c-api-connector/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Implement certificate authentication in ASP.NET Core for an Azure B2C API connector

This article shows how an ASP.NET Core API can be setup to require certificates for authentication. The API is used to implement an Azure B2C API connector service. The API connector client uses a certificate to request profile data from the Azure App Service API implementation, which is validated using the certificate thumbprint.

Code: https://github.com/damienbod/AspNetCoreB2cExtraClaims

Blogs in this series

Setup Azure App Service

An Azure App Service was created which uses .NET and 64 bit configurations. The Azure App Service is configured to require incoming client certificates and will forward this to the application. By configuring this, any valid certificate will work. The certificate still needs to be validated inside the application. You need to check that the correct client certificate is being used.

Implement the API with certificate authentication for deployment

The AddAuthentication sets the default scheme to CertificateAuthentication. The AddCertificate method adds the required configuration to validate the client certificates used with each request. We use a self signed certificate for the authentication. If a valid certificate is used, the MyCertificateValidationService is used to validate that it is also the correct certificate.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<MyCertificateValidationService>();
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices.GetService<MyCertificateValidationService>();
if (validationService != null && validationService.ValidateCertificate(context.ClientCertificate))
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
else
{
context.Fail("invalid cert");
}
return Task.CompletedTask;
}
};
});
builder.Host.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File(
//$@"../certauth.txt",
$@"D:\home\LogFiles\Application\{Environment.UserDomainName}.txt",
fileSizeLimitBytes: 1_000_000,
rollOnFileSizeLimit: true,
shared: true,
flushToDiskInterval: TimeSpan.FromSeconds(1)));

The middleware services are setup so that in development no authentication is used and the requests are validated using basic authentication. If the environment in not development, certificate authentication is used and all API calls require authorization.

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
if (!app.Environment.IsDevelopment())
{
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers().RequireAuthorization();
}
else
{
app.UseAuthorization();
app.MapControllers();
}
app.Run();

The MyCertificateValidationService validates the certificate. This checks if the certificate used has the correct thumbprint and is the same as the certificate used in the client application, in these case the Azure B2C API connector.

public class MyCertificateValidationService
{
private readonly ILogger<MyCertificateValidationService> _logger;
public MyCertificateValidationService(ILogger<MyCertificateValidationService> logger)
{
_logger = logger;
}
public bool ValidateCertificate(X509Certificate2 clientCertificate)
{
return CheckIfThumbprintIsValid(clientCertificate);
}
private bool CheckIfThumbprintIsValid(X509Certificate2 clientCertificate)
{
var listOfValidThumbprints = new List<string>
{
// add thumbprints of your allowed clients
"15D118271F9AE7855778A2E6A00A575341D3D904"
};
if (listOfValidThumbprints.Contains(clientCertificate.Thumbprint))
{
_logger.LogInformation($"Custom auth-success for certificate  {clientCertificate.FriendlyName} {clientCertificate.Thumbprint}");
return true;
}
_logger.LogWarning($"auth failed for certificate  {clientCertificate.FriendlyName} {clientCertificate.Thumbprint}");
return false;
}
}

Setup Azure B2C API connector with certification authentication

The Azure B2C API connector is setup to use a certificate. You can create the certificate anyway you want. I used the CertificateManager Nuget package to create a RSA 512 certificate with a 3072 key size. The thumbprint from this certificate needs to be validated in the ASP.NET Core API application.

The Azure B2C API connector is added to the Azure B2C user flow. The use flow requires all the custom claims to be defined and the values can be set in the API Connector service. See the first post in this blog group for details.

Creating an RSA 512 with a 3072 size key

You can create certificates using .NET Core using the CertificateManager Nuget package which provides some helper methods for creating the X509 certificates as required.

class Program
{
static CreateCertificates _cc;
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.AddUserSecrets<Program>();
var configuration = builder.Build();
var sp = new ServiceCollection()
.AddCertificateManager()
.BuildServiceProvider();
_cc = sp.GetService<CreateCertificates>();
var rsaCert = CreateRsaCertificateSha512KeySize2048("localhost", 10);
string password = configuration["certificateSecret"];
var iec = sp.GetService<ImportExportCertificate>();
var rsaCertPfxBytes = iec.ExportSelfSignedCertificatePfx(password, rsaCert);
File.WriteAllBytes("cert_rsa512.pfx", rsaCertPfxBytes);
Console.WriteLine("created");
}
public static X509Certificate2 CreateRsaCertificateSha512KeySize2048(string dnsName, int validityPeriodInYears)
{
var basicConstraints = new BasicConstraints
{
CertificateAuthority = false,
HasPathLengthConstraint = false,
PathLengthConstraint = 0,
Critical = false
};
var subjectAlternativeName = new SubjectAlternativeName
{
DnsName = new List<string>
{
dnsName,
}
};
var x509KeyUsageFlags = X509KeyUsageFlags.DigitalSignature;
// only if certification authentication is used
var enhancedKeyUsages = new OidCollection
{
OidLookup.ClientAuthentication,
// OidLookup.ServerAuthentication
// OidLookup.CodeSigning,
// OidLookup.SecureEmail,
// OidLookup.TimeStamping 
};
var certificate = _cc.NewRsaSelfSignedCertificate(
new DistinguishedName { CommonName = dnsName },
basicConstraints,
new ValidityPeriod
{
ValidFrom = DateTimeOffset.UtcNow,
ValidTo = DateTimeOffset.UtcNow.AddYears(validityPeriodInYears)
},
subjectAlternativeName,
enhancedKeyUsages,
x509KeyUsageFlags,
new RsaConfiguration
{
KeySize = 3072,
HashAlgorithmName = HashAlgorithmName.SHA512
});
return certificate;
}
}

Running the applications

I setup two user flows for running and testing the applications. One is using ngrok and local development with basic authentication. The second is using certification authentication and the deployed Azure App service. I published the API to the App service and run the UI application. When the user signs in, the API connector is used to get the extra custom claims from the deployed API and is returned.

Links:

https://docs.microsoft.com/en-us/azure/active-directory-b2c/api-connectors-overview?pivots=b2c-user-flow

https://docs.microsoft.com/en-us/azure/active-directory-b2c/

https://github.com/Azure-Samples/active-directory-dotnet-external-identities-api-connector-azure-function-validate/

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-customize-properties?pivots=dotnet-6-0

https://github.com/AzureAD/microsoft-identity-web/wiki




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK