I have two .NET 8 project, an ASP.NET Core 8 Web API and Blazor web app with interactive render mode set to server.
The Web API handles the authentication and provides a JWT token. The registration and login for getting a token works fine am trying to use permission base authorization using the sample from flexibleAuthorization.
The issue arises when I try to use @attribute [Authorize(Permissions.ViewAccessControl | Permissions.ConfigureAccessControl)] which returns a null authorizationHandlerContext user. I added it to a page.razor as a basic test but I was then met with an errorwhen i navigate to the page
enter image description here
@page "/configurations"
@using EstateAllocator.Web.Authorization
@using EstateAllocator.Web.Client.Models.Roles
@attribute [Authorize(Permissions.ViewAccessControl | Permissions.ConfigureAccessControl)]
<main class="p-4 md:ml-64 h-auto pt-20">
<EstateAllocator.Web.Components.Components.AccessControlComponent.RoleConfigurationComponent
/>
</main>
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSyncfusionBlazor();
services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
AddRootDirectory(services);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
// Set validation parameters
options.TokenValidationParameters = new TokenValidationParameters
{
// Validate issuer
ValidateIssuer = true,
// Validate audience
ValidateAudience = true,
// Validate expiration
ValidateLifetime = true,
// Validate signature
ValidateIssuerSigningKey = true,
// Set issuer
ValidIssuer = Configuration.GetSection("Jwt:Issuer").ToString(),
// Set audience
ValidAudience = Configuration.GetSection("Jwt:Audience").ToString(),
// Set signing key
IssuerSigningKey = new SymmetricSecurityKey(
// Get our secret key from configuration
Encoding.UTF8.GetBytes(Configuration.GetSection("Jwt:SecretKey").ToString())),
};
});
services.AddCascadingAuthenticationState();
services.AddOutputCache();
services.AddBlazoredLocalStorage();
services.AddAuthenticationCore();
services.AddHttpContextAccessor();
services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddSingleton<IAuthorizationPolicyProvider, FlexibleAuthorizationPolicyProvider>();
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;
namespace EstateAllocator.Web.Authorization
{
public class CustomAuthStateProvider(ProtectedLocalStorage localStorage) : AuthenticationStateProvider
{
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = (await localStorage.GetAsync<string>("authToken")).Value;
var identity = string.IsNullOrEmpty(token) ? new ClaimsIdentity() : GetClaimsIdentity(token);
var user = new ClaimsPrincipal(identity);
return new AuthenticationState(user);
}
public async Task MarkUserAsAuthenticated(string token)
{
await localStorage.SetAsync("authToken", token);
var identity = GetClaimsIdentity(token);
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
private ClaimsIdentity GetClaimsIdentity(string token)
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
var claims = jwtToken.Claims;
return new ClaimsIdentity(claims, "jwt");
}
public async Task MarkUserAsLoggedOut()
{
await localStorage.DeleteAsync("authToken");
var identity = new ClaimsIdentity();
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
}
}
using System.Threading.Tasks;
using EstateAllocator.Web.Client.Models.Roles;
using Microsoft.AspNetCore.Authorization;
namespace EstateAllocator.Web.Authorization;
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement)
{
var permissionClaim = context.User.FindFirst(
c => c.Type == CustomClaimTypes.Permissions);
if (permissionClaim == null)
{
return Task.CompletedTask;
}
if (!int.TryParse(permissionClaim.Value, out int permissionClaimValue))
{
return Task.CompletedTask;
}
var userPermissions = (Permissions)permissionClaimValue;
if ((userPermissions & requirement.Permissions) != 0)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
return Task.CompletedTask;
}
}
2
@attribute [Authorize(Permissions.ViewAccessControl | Permissions.ConfigureAccessControl)]
won’t work here.
It works before the CustomAuthenticationStateProvider. So the “permission” claim is not added when pass the authorize pipeline.
We should use AuthorizeView
, like below.
<AuthorizeView Roles="Your_Custom_Role">
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
@{
//if not authorize redirect to somewhere
NavigationManager.NavigateTo($"unauthorized");
}
</NotAuthorized>
</AuthorizeView>
Here is the realted links
1. Blazor Server .NET 8 with Windows Authentication and role authorization
2. blazor web app .net8 – negociate auth – http 403 when refresh page