Below is a clean, production-ready guide on how to authenticate a Blazor (Server OR WebAssembly) app using Azure Entra ID with Certificate-based Client Credential Authentication (App-to-App).
This covers:
✔ Using Azure Entra ID
✔ Registering the Blazor app
✔ Configuring certificate-based authentication
✔ Using MSAL
✔ Calling secured APIs
✔ Best practices for production
I’ve included both Blazor Server and Blazor WebAssembly flows where applicable.
1. Understanding the Authentication Flow
Azure Entra ID supports two authentications:
A) User Authentication (OpenID Connect)
When the app signs in users with Azure AD.
B) App Authentication (Client Credential Flow using Certificate)
App authenticates itself using a certificate.
You can combine both:
Users sign in using Azure AD
App (Blazor) calls downstream APIs using certificate-based client credentials
This is the most common enterprise setup.
2. Azure Setup (Required)
Step 1: Register Two Apps
You need:
(1) Blazor App Registration
For user login.
Redirect URIs:
https://localhost:7177/signin-oidc
Also add logout:
https://localhost:7177/signout-callback-oidc
(2) API or Protected Resource Registration
Or even Microsoft Graph.
This app will have:
Client ID
Tenant ID
Expose API Scopes
Certificate-based credentials (Upload Certificate)
3. Upload Certificate to Entra ID
Go to:
Azure Portal → App Registrations → (API App) → Certificates & Secrets → Upload Certificate
Upload
.cer(public certificate)Private
.pfxis stored only in your Blazor app (never upload).
4. Configure Blazor App to Use Certificate Authentication
For Blazor Server, use Microsoft.Identity.Web.
Install
dotnet add package Microsoft.Identity.Web
dotnet add package Microsoft.Identity.Web.UI
5. Configure appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "yourcompany.com",
"TenantId": "xxxx-xxxx-xxxx-xxxx",
"ClientId": "Blazor-App-Client-ID",
"CallbackPath": "/signin-oidc"
},
"AzureAdClientCert": {
"ClientId": "API-Client-ID",
"TenantId": "xxxx-xxxx-xxxx-xxxx",
"CertificateThumbprint": "YOUR_CERT_THUMBPRINT",
"Scopes": "api://API-APP-ID/.default"
}
6. Register Authentication in Program.cs (Blazor Server)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddRazorPages();
// Load certificate from store
X509Certificate2 clientCert = LoadCertificateFromStore(
builder.Configuration["AzureAdClientCert:CertificateThumbprint"]
);
// MSAL ConfidentialClient using certificate
builder.Services.AddSingleton<IConfidentialClientApplication>(provider =>
{
return ConfidentialClientApplicationBuilder
.Create(builder.Configuration["AzureAdClientCert:ClientId"])
.WithCertificate(clientCert)
.WithTenantId(builder.Configuration["AzureAdClientCert:TenantId"])
.Build();
});
// Token service
builder.Services.AddSingleton<TokenAcquisitionService>();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
7. Certificate Loader Utility
private static X509Certificate2 LoadCertificateFromStore(string thumbprint)
{
using var store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
return store.Certificates
.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false)
.OfType<X509Certificate2>()
.FirstOrDefault()
?? throw new Exception("Certificate not found");
}
8. Token Acquisition Service (using MSAL)
public class TokenAcquisitionService
{
private readonly IConfidentialClientApplication _client;
private readonly IConfiguration _config;
public TokenAcquisitionService(IConfidentialClientApplication client, IConfiguration config)
{
_client = client;
_config = config;
}
public async Task<string> GetAccessTokenAsync()
{
var scopes = _config["AzureAdClientCert:Scopes"].Split(' ');
var result = await _client
.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
}
9. Calling API from Blazor using Certificate-based Token
public class ApiClient
{
private readonly TokenAcquisitionService _tokenService;
private readonly HttpClient _http;
public ApiClient(TokenAcquisitionService tokenService, IHttpClientFactory httpFactory)
{
_tokenService = tokenService;
_http = httpFactory.CreateClient("Api");
}
public async Task<string> GetSecuredDataAsync()
{
string token = await _tokenService.GetAccessTokenAsync();
_http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
return await _http.GetStringAsync("/secured-endpoint");
}
}
10. For Blazor WebAssembly (WASM)
WASM cannot securely store a certificate → you must use:
✔ Backend API / BFF (Backend-for-Frontend pattern)
✔ Or Azure API Management
The certificate-based authentication must happen on server side, not in browser.
WASM can only do user authentication, not certificate authentication.
11. End-to-End Flow
User Login Flow (OIDC)
User → Blazor App → Azure AD → ID Token → Authenticated in UI
App-to-App Flow (Certificate)
Blazor App → Certificate → Azure AD Token Endpoint
→ Access Token → Call Downstream API
12. Recommended Architecture
[User]
↓ OIDC
[Blazor App] (Server)
↓ Certificate-based Client Credential
[Azure Entra ID]
↓ Access Token
[Protected API]
13. Security Best Practices
✔ Use PFX with password
✔ Store certificate in Azure Key Vault (Recommended)
✔ Rotate certificates every 6–12 months
✔ Never store cert in GitHub
✔ Use .default scope for client credentials

Join the conversation! Your thoughts help the community grow.