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:
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:
3. Upload Certificate to Entra ID
Go to:
Azure Portal → App Registrations → (API App) → Certificates & Secrets → Upload Certificate
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