Implement certificate authentication in ASP.NET Core for an Azure B2C API connec...
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/
https://github.com/AzureAD/microsoft-identity-web/wiki
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK