I have a Blazor Server app that uses Windows authentication to identify users. The app "detects" the user currently browsing the app and can display domain/username correctly.
I wrote a class that checks logged in username and fetches roles from a database, then updates the current principal so that the UI can properly take advantage of the given roles for the user.
I am avoiding EntityFramework and core identity scaffolding code for reasons I can't share here.
Here is the class I wrote
public class CompanyAuthentication
{
public CompanyAuthentication(AuthenticationStateProvider auth)
{
var result = auth.GetAuthenticationStateAsync().Result;
// here I have access to the current username:
string username = result.User.Identity.Name;
// now I can fetch roles from file, but for simplicity, I will use the following:
if(username.BeginsWith("admin"))
{
var claims = new ClaimsIdentity(new Claim[] {new Claim(ClaimType.Role, "admin")});
result.User.AddIdentity(claims); // this will have an effect on UI
}
}
}
I also added the above class as a service in Startup.cs
services.AddScoped<CompanyAuthentication>();
Now, in any razor page, I simply do:
@page "/counter"
@inject CompanyAuthentication auth
<AuthorizeView Roles="admin">
<Authorized> Welcome admin </Authorized>
<NotAuthorized> You are not authorized </NotAuthorized>
</AuthorizeView>
That all works fine, as long as I don't forget to inject CompanyAuthentication
in each page I intend to use.
Is there a way to automatically inject my class into every page without having to do @inject
?
I am aware that I can write a custom authentication class that inherits AuthenticationStateProvider
and then add it as service, but if I do that, I lose access to the currently logged in username, and thus, I can't fetch roles from the database.
I tried to use HttpContext.Current.User.Identity.Name
but that is not in the scope of any part in a Balzor server app.
How can I take advantage of Windows AuthenticationStateProvider and at the same time, customize its roles, without having to inject the customization everywhere ?
CodePudding user response:
You should be able to write a custom AuthenticationStateProvider
. I've set up a Blazor Server site with authentication set to Windows.
The custom AuthenticationStateProvider. Note it inherits from ServerAuthenticationStateProvider
.
public class MyAuthenticationStateProvider : ServerAuthenticationStateProvider
{
private bool _isNew = true;
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var state = await base.GetAuthenticationStateAsync();
// Add your code here to get the user info to use in the logic of what roles you add
// only add the extra Identity once
if (_isNew)
state.User.AddIdentity(AdminIdentity);
_isNew = false;
return state;
}
private ClaimsIdentity AdminIdentity
=> new ClaimsIdentity(new[] { new Claim(ClaimTypes.Role, "admin") }, "My Auth Type");
}
Register it in services - note the order:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// Add custom AuthenticationStateProvider after AddServerSideBlazor will overload the existing registered service
builder.Services.AddScoped<AuthenticationStateProvider, MyAuthenticationStateProvider>();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
And here's a test page to show you the claims:
@page "/"
@inject AuthenticationStateProvider Auth;
@using System.Security.Claims
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@if(user is not null)
{
@foreach(var claim in user.Claims)
{
<div>
@claim.Type : @claim.Value
</div>
}
}
@code{
private ClaimsPrincipal user = default!;
protected async override Task OnInitializedAsync()
{
var state = await Auth.GetAuthenticationStateAsync();
user = state.User;
}
}
Here's a screen capture showing both the user windows security info and the added Role.