Peter Forsbom
Peter Forsbom

Reputation: 95

Blazor Server .NET 8 with Windows Authentication and role authorization

I am migrating a Blazor server project from .NET 7 to .NET 8 and I am struggling with the Authorization. When I click on a link to my Admin page it is working fine but when I just go directly to the URL I get a 403 forbidden. I am guessing this has something to do with my routing. What I am missing?

Github sample

My simple AuthenticationStateProvider implementation:

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor)
    {
        this._httpContextAccessor = httpContextAccessor;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var identity = _httpContextAccessor.HttpContext.User.Identity;
        var windowsAccountName = identity.Name.Split('\\').Last();

        var ci = identity as ClaimsIdentity;
        var user = new ClaimsPrincipal(identity);

        user.AddIdentity(new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "Admin") }));
        user.AddIdentity(new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "Manager") }));
        return await Task.FromResult(new AuthenticationState(user));
    }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

//Windowes authentication setup
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
       .AddNegotiate();
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

My Admin.razor

@page "/admin"
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Authorization

@attribute [Authorize(Roles = "Admin")]

<h3>Admin Page</h3>

<AuthorizeView>
    <Authorized>
        <p>Hello, @context.User.Identity?.Name!</p>
    </Authorized>
    <NotAuthorized>
        <p>You're not authorized.</p>
    </NotAuthorized>
</AuthorizeView>

My Routes.razor

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
            <NotAuthorized>
                <h1>Unauthorized</h1>
                <p>Sorry, you are not authorized to view this page!</p>
            </NotAuthorized>
            <Authorizing>
                <h1>Please wait</h1>
                <p>You are being authorized...</p>
            </Authorizing>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

Upvotes: 1

Views: 482

Answers (2)

Peter Forsbom
Peter Forsbom

Reputation: 95

I think I actually found the solution in this link

I should not use @attribute [Authorize(Policy = "Admin")] because it works before my CustomAuthenticationStateProvider. When I use AuthorizeView it works!

<AuthorizeView Roles="Admin">
    <Authorized>
        <p>Hello, @context.User.Identity?.Name!</p>
    </Authorized>
    <NotAuthorized>
        @{
            //if not authorize redirect to somewhere
            NavigationManager.NavigateTo($"unauthorized");
        }
    </NotAuthorized>
</AuthorizeView>

Upvotes: 2

Jason Pan
Jason Pan

Reputation: 22029

After debugging the issue, I found a solution for this issue.

BlazorAuthorizationMiddlewareResultHandler

using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Authorization;

namespace BlazorAppWindowsAuth
{
    public class BlazorAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
    {
        public Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
        {
            return next(context);
        }
    }
}

Register it

using BlazorAppWindowsAuth;
using BlazorAppWindowsAuth.Components;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
//Windowes authentication setup
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
       .AddNegotiate();
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});


builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, BlazorAuthorizationMiddlewareResultHandler>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Got inspiration from this link.

One more thing, your CustomAuthenticationStateProvider method seems not correct. We may find the error User is not in the Admin role. Here is my working sample code.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

namespace BlazorAppWindowsAuth;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var originalIdentity = _httpContextAccessor.HttpContext.User.Identity;

        if (originalIdentity == null || !originalIdentity.IsAuthenticated)
        {
            return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
        }


        var claims = _httpContextAccessor.HttpContext.User.Claims.ToList();
        var claimsIdentity = new ClaimsIdentity(claims, originalIdentity.AuthenticationType);


        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Manager"));

        var user = new ClaimsPrincipal(claimsIdentity);
        return Task.FromResult(new AuthenticationState(user));
    }
}

Upvotes: 0

Related Questions