Home > Blockchain >  How to customize Default Windows AutheticationStateProvider in Blazor server app?
How to customize Default Windows AutheticationStateProvider in Blazor server app?

Time:06-27

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.

enter image description here

  • Related