I have a Blazor Server (using .NET 9 Blazor web app template with render mode set to server). I want to set Windows AD authentication, but it always outputs a null username on the user page, and no roles as well. I am part of domain though.
If I use
var user = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
then I was able to get the user name. But I also need to be able to get the roles of the user.
What am I missing in my code shown below?
Program.cs
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminsOnly", policy =>
policy.RequireRole("DOMAIN\\AdminGroup"));
});
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
User page
@page "/user"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider
<h3>User Information</h3>
@if (user != null)
{
<p>Username: @user.Identity.Name</p>
<ul>
@foreach (var claim in user.Claims)
{
<li>@claim.Type: @claim.Value</li>
}
</ul>
}
else
{
<p>Loading...</p>
}
@code {
private ClaimsPrincipal? user;
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
user = authState.User;
var username = user.Identity?.Name;
var roles = user.Claims.Where(c => c.Type == ClaimTypes.Role);
Console.WriteLine("Roles: " + string.Join(", ", roles.Select(r => r.Value)));
}
}
LaunchSettings.json
:
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:28505",
"sslPort": 44325
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5050",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"windowsAuthentication": true,
"anonymousAuthentication": false
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7150;http://localhost:5050",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"windowsAuthentication": true,
"anonymousAuthentication": false
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"windowsAuthentication": true,
"anonymousAuthentication": false
}
}
}
I may be totally wrong, but I don't think you get Roles through NTLM. There's no mapping from NT Groups to Roles.
What you get is a set of groupsid
:
Claim Type: http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid; Claim Value: S-1-5-4
Which you need to map manually into your roles.
Here's a simple example using a CustomAuthenticationStateProvider
which just checks the claims on the user and adds a new ClaimsIdentity
with the appropriate roles.
public class CustomAuthenticationStateProvider : ServerAuthenticationStateProvider
{
private readonly record struct AdGroupMap(string ClaimType, string Value, string Role);
private const string AdGroupClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid";
private readonly List<AdGroupMap> _adGroupMaps = new()
{
new AdGroupMap(AdGroupClaimType, "S-1-5-32-545", "Admin")
};
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var authState = await base.GetAuthenticationStateAsync();
var user = authState.User;
List<Claim> claims = new();
// Adds a new Identity to the ClaimsPrincipal with the Group pset
foreach (var adGroupMap in _adGroupMaps)
{
if (user.Claims.Any(claim => claim.Type == adGroupMap.ClaimType && claim.Value == adGroupMap.Value))
claims.Add(new Claim(ClaimTypes.Role, adGroupMap.Role)) ;
}
if (claims.Any())
user.AddIdentity(new ClaimsIdentity(claims));
// return the modified principal
return await Task.FromResult(new AuthenticationState(user));
}
}
The demo repo is here: https://github.com/ShaunCurtis/SO79394377