fix(manager): redirect unauth nav to login instead of 401
AddAuthBlocks installs JwtBearer as the default challenge scheme; the
authorization middleware 401s unauthenticated nav requests before the
Blazor router runs. Tokens live in localStorage and are only readable
via JS interop after the SignalR circuit is live.
- Program.cs: MapRazorComponents .AllowAnonymous() so nav reaches the
Blazor router; API surfaces (MapAuthBlocks, MapControllers) still
enforce JWT. Fix middleware order to UseAuthentication -> UseAntiforgery
-> UseAuthorization per Blazor Web App template.
- App.razor: InteractiveServerRenderMode(prerender:false) on Routes and
HeadOutlet so AuthorizeRouteView evaluates after JS interop is ready;
extract to static field (was two inline allocations per render cycle).
- CmsLayout/Pages: drop conflicting per-component @rendermode directives
(parent now owns the render mode).
- Routes.razor: break authenticated-but-wrong-role redirect loop; split
NotAuthorized into unauthenticated -> RedirectToLogin and
authenticated-wrong-role -> RedirectToAccessDenied (new component).
- Pages/Index.razor: deleted — NavigateTo('/cms') was unreachable for
unauthenticated users and racey for authorized ones.
This commit is contained in:
@@ -12,12 +12,16 @@
|
||||
<link rel="stylesheet" href="@Assets["_content/DeepDrftShared.Client/styles/deepdrft-tokens.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/ico" href="deepdrft-logo.ico" />
|
||||
<HeadOutlet />
|
||||
<HeadOutlet @rendermode="ServerMode" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<Routes @rendermode="ServerMode" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src=@Assets["_content/MudBlazor/MudBlazor.min.js"]></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@code {
|
||||
private static readonly IComponentRenderMode ServerMode = new InteractiveServerRenderMode(prerender: false);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@rendermode InteractiveServer
|
||||
@inherits LayoutComponentBase
|
||||
@using DeepDrftShared.Client.Common
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@page "/cms"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])]
|
||||
|
||||
<PageTitle>DeepDrft CMS</PageTitle>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
@page "/"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])]
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NavigationManager.NavigateTo("/cms", replace: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NavigationManager.NavigateTo("/Account/AccessDenied", replace: true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,15 @@
|
||||
AdditionalAssemblies="new[] { typeof(AuthBlocksWeb._Imports).Assembly }">
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="routeData">
|
||||
<NotAuthorized>
|
||||
<RedirectToLogin />
|
||||
<NotAuthorized Context="authState">
|
||||
@if (authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
<RedirectToAccessDenied />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RedirectToLogin />
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
|
||||
@@ -156,8 +156,8 @@ if (!app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseAntiforgery();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapStaticAssets();
|
||||
|
||||
@@ -168,9 +168,17 @@ app.MapAuthBlocks();
|
||||
// Mounts CMS mutation controllers (CmsUploadController, CmsEditController, CmsDeleteController).
|
||||
app.MapControllers();
|
||||
|
||||
// Blazor page authorization is owned by AuthorizeRouteView in Routes.razor, not
|
||||
// ASP.NET Core endpoint authorization. AuthBlocks tokens live in browser localStorage
|
||||
// (read via JS interop by JwtAuthenticationStateProvider), so the JWT never reaches
|
||||
// the server on a navigation request. Without AllowAnonymous here, the JwtBearer
|
||||
// challenge for an unauthenticated nav returns 401 before the Blazor router runs,
|
||||
// short-circuiting the NotAuthorized -> RedirectToLogin path. JWT enforcement
|
||||
// remains in force for the API surfaces (MapAuthBlocks, MapControllers).
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode()
|
||||
.AddAdditionalAssemblies(typeof(AuthBlocksWeb._Imports).Assembly);
|
||||
.AddAdditionalAssemblies(typeof(AuthBlocksWeb._Imports).Assembly)
|
||||
.AllowAnonymous();
|
||||
|
||||
app.Run();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user