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?
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>
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));
}
}