Fix Blazor AD Impersonation: 5 Steps to Success 2025
Struggling with Blazor AD impersonation? Our updated 2025 guide provides 5 clear steps to fix authentication, handle the double-hop, and succeed.
Daniel Carter
Senior .NET architect specializing in enterprise security and authentication.
So, you’re building a sleek, modern enterprise application with Blazor. It’s fast, it’s C# from front to back, and life is good... until you hit the wall. The wall of Active Directory impersonation. Suddenly, your app, which runs perfectly on your machine, can’t access that critical network share or legacy database when deployed. The app pool identity just doesn’t have the rights, and you need your application to act on behalf of the user. Sound familiar?
You’re not alone. Getting Blazor and Windows Authentication to play nicely with impersonation is a classic hurdle. The documentation can feel scattered, and old forum posts often lead you down a rabbit hole of outdated techniques. But fear not. We’re going to walk through a clear, modern, five-step process to nail Blazor AD impersonation in 2025, from basic setup to conquering the dreaded "double-hop" problem.
Step 1: Laying the Groundwork: Project & Server Configuration
Before you write a single line of C# for impersonation, you have to tell your web server what you’re trying to do. Impersonation is fundamentally a server-side concept, which means this guide is geared towards Blazor Server applications. The core idea is to let Windows handle the initial authentication handshake.
Your first stop is the launchSettings.json
file in your project’s Properties
folder. This file controls how your app behaves when you run it from Visual Studio. You need to make two critical changes to the profile you’re using (usually "https" or "IIS Express"):
- Set
"anonymousAuthentication"
tofalse
. - Set
"windowsAuthentication"
totrue
.
Your configuration profile should look something like this:
{
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7123;http://localhost:5123",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:54321",
"sslPort": 44321
}
}
}
}
}
When you deploy to a full IIS server, you’ll need to mirror these settings in the "Authentication" feature for your site: disable Anonymous Authentication and enable Windows Authentication. This foundational step ensures that IIS and Kestrel (ASP.NET Core's web server) correctly identify the user from their Windows login.
Step 2: Configuring Authentication in Your App's Pipeline
Next, it’s time to tell your Blazor app how to understand and process the Windows credentials passed along by the server. This happens in your Program.cs
file.
First, you need to add the authentication services. We'll use the Negotiate package, which handles both Kerberos and NTLM negotiation for you. Add this line to your service configuration:
// Add services to the container.
bilder.Services.AddAuthentication(Microsoft.AspNetCore.Authentication.Negotiate.NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
This tells your app to use the Negotiate scheme as its default method for authenticating users. Then, you need to add the corresponding middleware to the request pipeline. Order is critical here. The authentication and authorization middleware must come after routing but before you map your Blazor hub and endpoints.
var app = builder.Build();
// ... other middleware
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
With these pieces in place, your application now correctly identifies the logged-in user. You can verify this by injecting an AuthenticationStateProvider
into a component and checking authState.User.Identity.Name
. But here's the catch: your app's process is still running as the application pool identity (like IIS APPPOOL\YourApp
). Simply being authenticated isn't the same as acting as the user.
Step 3: The Heart of the Matter: Implementing Impersonation Logic
This is where the magic happens. To perform actions as the user, you must explicitly impersonate their WindowsIdentity
. The most robust way to handle this is by wrapping the impersonation logic in a dedicated service, making it clean, reusable, and testable.
Creating a Reusable Impersonation Service
First, you need access to the current HTTP context to get the user's identity. Register the IHttpContextAccessor
in Program.cs
:
builder.Services.AddHttpContextAccessor();
Now, let’s create our service. This service will expose a method that accepts an action or function to execute within the impersonated context.
using System.Security.Principal;
public class ImpersonationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ImpersonationService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task RunAsUserAsync(Func<Task> work)
{
var user = _httpContextAccessor.HttpContext?.User;
if (user?.Identity is not WindowsIdentity windowsIdentity)
{
throw new InvalidOperationException("Cannot impersonate. The user is not a Windows user.");
}
// The magic is here! This runs the provided 'work' under the user's security context.
await WindowsIdentity.RunImpersonatedAsync(windowsIdentity.AccessToken, work);
}
// Optional: A synchronous version for non-async tasks
public void RunAsUser(Action work)
{
var user = _httpContextAccessor.HttpContext?.User;
if (user?.Identity is not WindowsIdentity windowsIdentity)
{
throw new InvalidOperationException("Cannot impersonate. The user is not a Windows user.");
}
WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, work);
}
}
Finally, register this new service as scoped in Program.cs
. A scoped lifetime is perfect here, as it aligns with the Blazor circuit and HTTP request lifecycle.
builder.Services.AddScoped<ImpersonationService>();
Step 4: Putting It to Work: Accessing Secured Resources
With our service ready, using it is incredibly straightforward. Let’s say you have a Blazor component that needs to read a text file from a network share (\\fileserver\reports\daily.txt
) that only specific domain users can access.
You can inject the ImpersonationService
directly into your component and wrap the file access code within a call to RunAsUserAsync
.
@page "/reports"
@using System.IO
@inject ImpersonationService Impersonator
@inject ILogger<Reports> Logger
<h3>Daily Report</h3>
<button class="btn btn-primary" @onclick="LoadReport">Load Report</button>
@if (!string.IsNullOrEmpty(_reportContent))
{
<pre>@_reportContent</pre>
}
@if (!string.IsNullOrEmpty(_errorMessage))
{
<p class="text-danger">@_errorMessage</p>
}
@code {
private string? _reportContent;
private string? _errorMessage;
private async Task LoadReport()
{
_reportContent = null;
_errorMessage = null;
try
{
await Impersonator.RunAsUserAsync(async () =>
{
// This block of code runs as the authenticated user!
var path = "\\\\fileserver\\reports\\daily.txt";
_reportContent = await File.ReadAllTextAsync(path);
Logger.LogInformation($"Successfully read file as {WindowsIdentity.GetCurrent().Name}");
});
}
catch (Exception ex)
{
_errorMessage = $"Failed to load report: {ex.Message}";
Logger.LogError(ex, "Impersonation or file access failed.");
}
}
}
Without the impersonation wrapper, this code would fail with an "Access Denied" error because the app pool identity lacks permission. Inside the wrapper, the code executes with the user's permissions, and it just works.
Step 5: The Final Boss: Kerberos and the Double-Hop Problem
You’ve got it working! Your Blazor app can now access network files. But then you try to access a SQL Server database on another machine from within your impersonated code... and it fails. Welcome to the infamous "double-hop" problem.
Here’s the breakdown:
- Hop 1: Client PC -> Web Server (This works fine)
- Hop 2: Web Server -> Database Server (This fails)
The issue is that the default authentication protocol, NTLM, is designed for security and cannot delegate your credentials to a third machine. To solve this, you need to enable Kerberos, which is designed for exactly this kind of delegation.
Setting up Kerberos is a sysadmin task, not a coding one, but as a developer, you need to know what to ask for. The key is configuring Service Principal Names (SPNs) in Active Directory.
Quick Kerberos Checklist:
- SPNs for the Web Server: Your web server's machine account needs SPNs that identify its HTTP service. For a server named
WEBPROD01
, you'd need SPNs likeHTTP/webprod01
andHTTP/webprod01.yourdomain.com
. - SPNs for the Backend Service: Your SQL Server needs SPNs for its service account (e.g.,
MSSQLSvc/sqlprod01.yourdomain.com:1433
). - Delegation Configuration: In Active Directory Users and Computers, find the web server's computer object. On the "Delegation" tab, you must configure it to trust the server for delegation. The most secure option is "Trust this computer for delegation to specified services only" (Constrained Delegation), where you explicitly allow it to delegate credentials to the SQL Server's SPN.
Once your domain administrator has configured Kerberos delegation correctly, your double-hop scenario will work without any additional code changes. Your impersonated code will now be able to access the SQL Server seamlessly.
Bringing It All Together
Fixing Blazor AD impersonation isn't about a single magic bullet; it's about methodically layering the right configurations. By setting up your server, configuring the ASP.NET Core pipeline, creating a clean service for impersonation logic, and understanding the Kerberos requirements for complex scenarios, you can build powerful, secure enterprise Blazor apps that fully leverage the power of Active Directory. It might seem daunting, but by following these steps, you’re well on your way to success.