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:
Daniel Harvey
2026-05-24 18:29:07 -04:00
parent e6dc15e451
commit 95772c655e
7 changed files with 35 additions and 19 deletions
+10 -2
View File
@@ -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();