From 95772c655eaad757ba5349bd3b3f03b4d40bf393 Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Sun, 24 May 2026 18:29:07 -0400 Subject: [PATCH] fix(manager): redirect unauth nav to login instead of 401 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- DeepDrftManager/Components/App.razor | 8 ++++++-- DeepDrftManager/Components/Layout/CmsLayout.razor | 1 - DeepDrftManager/Components/Pages/Cms/Index.razor | 1 - DeepDrftManager/Components/Pages/Index.razor | 11 ----------- .../Components/RedirectToAccessDenied.razor | 10 ++++++++++ DeepDrftManager/Components/Routes.razor | 11 +++++++++-- DeepDrftManager/Program.cs | 12 ++++++++++-- 7 files changed, 35 insertions(+), 19 deletions(-) delete mode 100644 DeepDrftManager/Components/Pages/Index.razor create mode 100644 DeepDrftManager/Components/RedirectToAccessDenied.razor diff --git a/DeepDrftManager/Components/App.razor b/DeepDrftManager/Components/App.razor index 845821d..3db6cf3 100644 --- a/DeepDrftManager/Components/App.razor +++ b/DeepDrftManager/Components/App.razor @@ -12,12 +12,16 @@ - + - + + +@code { + private static readonly IComponentRenderMode ServerMode = new InteractiveServerRenderMode(prerender: false); +} diff --git a/DeepDrftManager/Components/Layout/CmsLayout.razor b/DeepDrftManager/Components/Layout/CmsLayout.razor index 619e6fe..6183904 100644 --- a/DeepDrftManager/Components/Layout/CmsLayout.razor +++ b/DeepDrftManager/Components/Layout/CmsLayout.razor @@ -1,4 +1,3 @@ -@rendermode InteractiveServer @inherits LayoutComponentBase @using DeepDrftShared.Client.Common diff --git a/DeepDrftManager/Components/Pages/Cms/Index.razor b/DeepDrftManager/Components/Pages/Cms/Index.razor index f3283ef..dc91a69 100644 --- a/DeepDrftManager/Components/Pages/Cms/Index.razor +++ b/DeepDrftManager/Components/Pages/Cms/Index.razor @@ -1,5 +1,4 @@ @page "/cms" -@rendermode InteractiveServer @attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])] DeepDrft CMS diff --git a/DeepDrftManager/Components/Pages/Index.razor b/DeepDrftManager/Components/Pages/Index.razor deleted file mode 100644 index 78e98ef..0000000 --- a/DeepDrftManager/Components/Pages/Index.razor +++ /dev/null @@ -1,11 +0,0 @@ -@page "/" -@rendermode InteractiveServer -@attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])] -@inject NavigationManager NavigationManager - -@code { - protected override void OnInitialized() - { - NavigationManager.NavigateTo("/cms", replace: true); - } -} diff --git a/DeepDrftManager/Components/RedirectToAccessDenied.razor b/DeepDrftManager/Components/RedirectToAccessDenied.razor new file mode 100644 index 0000000..51d2fb8 --- /dev/null +++ b/DeepDrftManager/Components/RedirectToAccessDenied.razor @@ -0,0 +1,10 @@ +@inject NavigationManager NavigationManager + +@code { + + protected override void OnInitialized() + { + NavigationManager.NavigateTo("/Account/AccessDenied", replace: true); + } + +} diff --git a/DeepDrftManager/Components/Routes.razor b/DeepDrftManager/Components/Routes.razor index a5d2f07..360d3db 100644 --- a/DeepDrftManager/Components/Routes.razor +++ b/DeepDrftManager/Components/Routes.razor @@ -2,8 +2,15 @@ AdditionalAssemblies="new[] { typeof(AuthBlocksWeb._Imports).Assembly }"> - - + + @if (authState.User.Identity?.IsAuthenticated == true) + { + + } + else + { + + } diff --git a/DeepDrftManager/Program.cs b/DeepDrftManager/Program.cs index 347d9ee..4edf02e 100644 --- a/DeepDrftManager/Program.cs +++ b/DeepDrftManager/Program.cs @@ -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() .AddInteractiveServerRenderMode() - .AddAdditionalAssemblies(typeof(AuthBlocksWeb._Imports).Assembly); + .AddAdditionalAssemblies(typeof(AuthBlocksWeb._Imports).Assembly) + .AllowAnonymous(); app.Run();