Client Certificate Authentication
Use mutual TLS (mTLS) client certificates to authenticate callers.
This guide focuses on how client certificates work in Kestrun, how to configure Kestrel TLS behavior vs. ASP.NET Core authentication, and how to make self-signed development setups work without weakening production security.
1. Two layers: TLS handshake vs authentication
Client certificates involve two distinct layers:
- TLS handshake (Kestrel): the client presents a certificate during the TLS handshake. Kestrel decides whether to accept that certificate.
- Authentication (Kestrun / ASP.NET Core): once the request is established, the certificate is exposed as
Connection.ClientCertificateand the certificate authentication handler creates an authenticated principal (claims).
If the TLS handshake rejects the certificate, the request never reaches Kestrun.
Why this matters
- You can have
Add-KrClientCertificateAuthenticationconfigured correctly and still fail at TLS. - Self-signed client certs often fail TLS validation by default unless you loosen validation or trust the issuing CA.
2. Quick start (development)
For a working, end-to-end example that:
- creates a self-signed server certificate,
- creates a self-signed client certificate,
- configures TLS to accept self-signed client certs in dev,
- requires certificate authentication only on selected routes,
use the tutorial sample:
- Tutorial sample:
pwsh/tutorial/examples/8.8-Client-Certificate-Authentication.ps1 - Tutorial page: Client Certificate tutorial
3. Configure TLS client certificate handling (Kestrel)
Kestrel is configured via Set-KrServerHttpsOptions.
3.1 Client certificate mode
-ClientCertificateMode controls whether the client is required to present a certificate:
NoCertificate: never request a client cert.AllowCertificate: request a client cert, but allow connections with none.RequireCertificate: fail the TLS handshake if the client does not present a certificate.
Recommendation
- Prefer
AllowCertificateand enforce authentication on protected routes. This keeps public endpoints (like/infoor health probes) working without a client cert. - Use
RequireCertificateonly when you truly want every connection to be mTLS.
3.2 TLS validation callback (required for many dev setups)
Kestrel exposes a TLS validation callback:
- Delegate:
Func<X509Certificate2, X509Chain, SslPolicyErrors, bool>
In Kestrun, you can provide it in three ways:
- Built-in .NET callbacks (recommended when a simple policy is enough)
- Roslyn-compiled code snippet (
-ClientCertificateValidationCode) - Roslyn-compiled code snippet from file (
-ClientCertificateValidationCodePath)
Important: the TLS callback runs on Kestrel TLS threads during the handshake. It must be fast, thread-safe, and must not rely on PowerShell runspaces.
Option A: Built-in .NET callback
Set-KrServerHttpsOptions `
-ClientCertificateMode AllowCertificate `
-ClientCertificateValidation ([Kestrun.Certificates.ClientCertificateValidationCallbacks]::AllowMissingOrSelfSignedForDevelopment)
Option B: Compile C# snippet (method body)
$code = @'
if (certificate is null) return true; // AllowCertificate mode
return sslPolicyErrors == SslPolicyErrors.None
|| sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors; // dev only
'@
Set-KrServerHttpsOptions `
-ClientCertificateMode AllowCertificate `
-ClientCertificateValidationLanguage CSharp `
-ClientCertificateValidationCode $code
Option C: Compile from file path (language inferred)
.cs/.csx=> C#.vb=> VB.NET
Set-KrServerHttpsOptions `
-ClientCertificateMode AllowCertificate `
-ClientCertificateValidationCodePath './client-cert-validation.cs'
3.3 Endpoint-level requirement
If you want to require client certificates at the listener level, you can combine:
Set-KrServerHttpsOptions -ClientCertificateMode RequireCertificate- Listener configuration that requires a client certificate (when applicable)
However, route-level enforcement is usually more flexible and works well with AllowCertificate.
4. Configure certificate authentication (Kestrun)
Enable certificate auth via Add-KrClientCertificateAuthentication.
4.1 Common options
Add-KrClientCertificateAuthentication `
-AuthenticationScheme 'Certificate' `
-AllowedCertificateTypes All `
-RevocationMode NoCheck
Key points:
-AuthenticationSchemelets you name the scheme used in routes.-AllowedCertificateTypescontrols whether self-signed certs are acceptable.-RevocationModecontrols CRL/OCSP behavior.
4.2 Route enforcement
To protect a route, specify the certificate scheme:
Add-KrMapRoute -Verbs Get -Pattern '/secure/cert/hello' -AuthorizationScheme 'Certificate' -ScriptBlock {
$cert = $Context.Connection.ClientCertificate
Write-KrJsonResponse @{ subject = $cert.Subject; thumbprint = $cert.Thumbprint }
}
When using AllowCertificate, callers without a client certificate can still connect to the server, but protected routes will return 401.
4.3 Claims
The certificate handler issues claims such as:
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/namethumbprintissuerserialnumber
Use $Context.User.Claims to inspect what was issued.
5. Trust and validation strategies
5.1 Production
Recommended production approach:
- Use a client certificate issued by a trusted CA (internal PKI is fine).
- Ensure the server trusts the CA chain (OS trust store or explicit trust store).
- Prefer strict TLS validation (
SslPolicyErrors.None) and enable revocation checking as appropriate. - Use certificate authentication to map identity and authorization.
Avoid accepting RemoteCertificateChainErrors in production.
5.2 Development / local
Options for local dev:
- Trust your dev CA in the OS trust store (best, closest to production behavior).
- Or relax TLS validation using the TLS callback (fastest for tutorials and CI).
If you relax TLS validation, keep it:
- scoped to local development environments,
- documented and easy to remove,
- paired with route-level auth enforcement.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Client: “The decryption operation failed” | TLS handshake rejected the client certificate | Configure TLS callback with Set-KrServerHttpsOptions |
| Server: RemoteCertificateValidationCallback… | Self-signed/untrusted client cert fails chain validation | Use a dev callback or trust the CA |
| “There is no Runspace available” | TLS callback implemented as PowerShell script block | Use a .NET delegate or Roslyn-compiled snippet |
| Public endpoints require a client certificate | TLS mode is RequireCertificate | Prefer AllowCertificate and enforce per-route |
| 401 on protected route with a cert | Auth scheme mismatch or cert auth not registered | Align -AuthenticationScheme and -AuthorizationScheme |
7. References
- Guides:
- Tutorials:
- PowerShell cmdlets:
- External:
Return to the Authentication index.