Blazor Server (.NET 8) redirect fails after login

Ryan Anderson 25 Reputation points
2025-02-05T22:16:40.9766667+00:00

We have a Blazor Server app (.NET 8) that is hosted on IIS. Users access it by logging into Citrix with their Active Directory credentials, then into the app (with those same credentials). We use ASP.NET Identity to manage login. Both the end-users and myself are on a different Windows domain than what the VM is joined to.

When I access the application with my own account I can get in without an issue. But when end-users try to log in I can see that they authenticate correctly within the app, but they receive an error in the browser when Blazor tries to redirect them to the main page. For some users this is "HTTP Error 400. The size of the request headers is too long", for others it's simply a default MS Edge "Hmm... cannot reach this page" message, with ERR_HTTP2_PROTOCOL_ERROR as the code. In either case, they can't even return to the login screen until they delete the ".AspNetCore.Identity.Application" cookie.

I've seen a lot of suggestions that the authentication cookie needs to be trimmed down, so to validate that I created a basic application that just generates a dummy cookie and we get the same result. That dummy cookie is under 800 bytes, which should be well below any default threshold.

Windows development | Internet Information Services
Developer technologies | ASP.NET | ASP.NET Core
Developer technologies | .NET | Blazor
Windows for business | Windows Server | User experience | Other
{count} votes

1 answer

Sort by: Most helpful
  1. MohaNed Ghawar 155 Reputation points
    2025-02-06T15:08:13.51+00:00

    This issue appears to be related to authentication cookies and cross-domain communication in a Citrix environment with different Windows domains.

    1. First, modify your Program.cs to properly configure the authentication cookie settings:
    builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies(options =>
    {
        // Configure application cookie
        options.ApplicationCookie.Configure(cookie =>
        {
            // Set cookie properties
            cookie.Cookie.Name = ".AspNetCore.Identity.Application";
            cookie.Cookie.SameSite = SameSiteMode.Lax;
            cookie.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            
            // Set sliding expiration
            cookie.ExpireTimeSpan = TimeSpan.FromHours(8);
            cookie.SlidingExpiration = true;
            
            // Important: Handle forbidden and redirect
            cookie.Events = new CookieAuthenticationEvents
            {
                OnRedirectToLogin = context =>
                {
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                },
                OnRedirectToAccessDenied = context =>
                {
                    context.Response.StatusCode = StatusCodes.Status403Forbidden;
                    return Task.CompletedTask;
                }
            };
        });
    });
    // Add custom claims transformation to reduce cookie size
    builder.Services.AddScoped<IClaimsTransformation, MinimalClaimsTransformation>();
    
    1. Create a minimal claims transformation to reduce cookie size:
         public class MinimalClaimsTransformation : IClaimsTransformation
         {
             public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
             {
                 var identity = (ClaimsIdentity)principal.Identity;
                 
                 // Keep only essential claims
                 var essentialClaims = new[] 
                 { 
                     ClaimTypes.NameIdentifier,
                     ClaimTypes.Name,
                     ClaimTypes.Email,
                     ClaimTypes.Role
                 };
                 
                 var claims = identity.Claims
                     .Where(c => essentialClaims.Contains(c.Type))
                     .ToList();
                     
                 var newIdentity = new ClaimsIdentity(
                     claims,
                     identity.AuthenticationType,
                     ClaimTypes.Name,
                     ClaimTypes.Role);
                     
                 return Task.FromResult(new ClaimsPrincipal(newIdentity));
             }
         }
      
    2. Add IIS configuration in web.config:
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <system.webServer>
        <security>
          <requestFiltering>
            <!-- Increase header size limits -->
            <requestLimits maxQueryString="8192" maxUrl="8192" maxAllowedContentLength="30000000" />
          </requestFiltering>
        </security>
        <handlers>
          <remove name="aspNetCore" />
          <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
        </handlers>
        <aspNetCore processPath="dotnet" 
                    arguments=".\YourApp.dll" 
                    stdoutLogEnabled="false" 
                    stdoutLogFile=".\logs\stdout"
                    hostingModel="inprocess">
          <environmentVariables>
            <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
          </environmentVariables>
        </aspNetCore>
      </system.webServer>
    </configuration>
    
    
    1. Modify your App.razor to handle authentication state:
         <CascadingAuthenticationState>
             <Router AppAssembly="@typeof(App).Assembly">
                 <Found Context="routeData">
                     <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                         <NotAuthorized>
                             @if (context.User.Identity?.IsAuthenticated != true)
                             {
                                 <RedirectToLogin />
                             }
                             else
                             {
                                 <p>You are not authorized to access this resource.</p>
                             }
                         </NotAuthorized>
                         <Authorizing>
                             <div class="loading">Loading...</div>
                         </Authorizing>
                     </AuthorizeRouteView>
                 </Found>
                 <NotFound>
                     <PageTitle>Not found</PageTitle>
                     <LayoutView Layout="@typeof(MainLayout)">
                         <p>Sorry, there's nothing at this address.</p>
                     </LayoutView>
                 </NotFound>
             </Router>
         </CascadingAuthenticationState>
      
    2. Add these settings to your appsettings.json:
         {
           "DetailedErrors": true,
           "Logging": {
             "LogLevel": {
               "Default": "Information",
               "Microsoft.AspNetCore": "Warning"
             }
           },
           "AllowedHosts": "*",
           "Authentication": {
             "Cookie": {
               "MaxAge": 480,  // 8 hours in minutes
               "SlidingExpiration": true
             }
           }
         }
      

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.