Fixing the auth‑auth loop in .NET 10 Blazor Server apps

Summary

A critical Authentication-Authorization Loop was identified in a .NET 10 Blazor Web App using InteractiveServer render mode. Users navigating to the /Account/Login route experienced a “flicker” effect where the login page would render briefly before being forcibly redirected or disappearing. This issue stems from a fundamental mismatch between Static SSR (Server-Side Rendering) identity endpoints and Interactive Blazor routing logic.

Root Cause

The failure is caused by a Race Condition between the ASP.NET Core Identity middleware and the Blazor Router’s authorization logic:

  • The Prerendering Conflict: By default, Blazor components undergo Prerendering. During this phase, the server renders the HTML statically. The page correctly identifies the user is unauthenticated and renders the [AllowAnonymous] login component.
  • Hydration vs. Authorization: Once the page “hydrates” (becomes interactive via SignalR), the Blazor Router takes control of the client-side state.
  • The Loop: The <AuthorizeRouteView> component evaluates the current user state via the CascadingAuthenticationState. Because the interactive circuit is fresh, it may briefly perceive the state as “unauthenticated” and trigger the <NotAuthorized> template.
  • The Redirect Trap: The <NotAuthorized> template contains a <RedirectToLogin /> component. This component forces a navigation to /Account/Login. Since the user is already on /Account/Login, the router attempts to navigate to the same route, creating a logic loop or a state mismatch that causes the component to unmount or redirect to a blank page.

Why This Happens in Real Systems

In complex distributed systems, this is a classic case of State Inconsistency between the Transport Layer and the Application Layer:

  • Identity is Cookie-Based (HTTP): ASP.NET Identity relies on HTTP Cookies, which are handled by the web server’s middleware.
  • Blazor is Connection-Based (SignalR): Interactive Server components rely on a persistent WebSocket connection.
  • The Synchronization Gap: The “Source of Truth” for authentication exists in the HTTP headers, but the Blazor Circuit manages its own internal representation of the ClaimsPrincipal. When the circuit establishes, there is a microscopic window where the Circuit’s view of the user is being synchronized with the Middleware’s view of the user.

Real-World Impact

  • Broken User Experience: Users are physically unable to log in because the UI elements they need to interact with (input fields, submit buttons) are unmounted by the router before they can be used.
  • Increased Server Load: Rapid-fire redirection loops consume CPU cycles and SignalR connection bandwidth.
  • Security Confusion: Developers often mistake this for a session/cookie issue, leading them to spend hours debugging JWTs or Cookie expiration when the issue is actually Client-side Routing.

Example or Code

To fix this, the <NotAuthorized> logic must explicitly ignore the login route to prevent the redirect loop.


    
        
            
                
                    @if (NavigationManager.Uri != NavigationManager.BaseUri + "Account/Login")
                    {
                        
                    }
                    else
                    {
                        

You are not authorized to access this resource.

}

Not found

@code { [Inject] private NavigationManager NavigationManager { get; set; } = default!; }

How Senior Engineers Fix It

Senior engineers look beyond the immediate symptom and address the Architectural Pattern:

  1. Route Exclusion: They implement logic within the AuthorizeRouteView to ensure that if the current URI matches the login path, the NotAuthorized redirection logic is bypassed.
  2. Hybrid Rendering Strategy: Instead of making the entire app Interactive Server, they use Static SSR for Identity/Account pages and Interactive modes only for the actual application dashboard. This ensures authentication flows through standard HTTP POST/Redirect cycles, which are more robust.
  3. State Synchronization: They ensure that AuthenticationStateProvider is correctly implemented to bridge the gap between the initial HTTP request and the subsequent SignalR circuit.

Why Juniors Miss It

  • Focusing on the Wrong Layer: Juniors often assume the problem is with the Identity Configuration (e.g., AddIdentityCore) or Cookie Settings, rather than the UI Routing logic.
  • Over-reliance on Interactivity: They tend to enable InteractiveServer globally, not realizing that certain parts of an application (like Auth/Login) are inherently better suited for Static SSR.
  • Missing the “Hydration” Concept: They view the page as a single static entity, whereas senior engineers view it as a two-stage process: Static HTML followed by Active SignalR Circuit.

Leave a Comment