I have a Blazor Server WebApp, with the render mode set accordingly, that is, in "/Account/" pages, the @rendermode=null, and set to @rendermode=InteractiveServer otherwise.
You can tell the render mode through App.razor:
<!DOCTYPE html><html lang="en"><head><link href=@Assets["_content/MudBlazor/MudBlazor.min.css"] rel="stylesheet" /><ImportMap /><HeadOutlet @rendermode="RenderModeForPage" /></head><body><Routes @rendermode="RenderModeForPage" /><script src="_framework/blazor.web.js"></script><script src=@Assets["_content/MudBlazor/MudBlazor.min.js"]></script><script src=@Assets["_content/Extensions.MudBlazor.StaticInput/NavigationObserver.js"]></script></body></html>@code { [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") ? null : InteractiveServer;}For the first time login, everything work perfect: An Antiforgery Token was generated and user cookie set to browser.After I logged out and tried to logged in again, an exception thrown said:
Login failed: Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.If I click login button again, it turned to an page with a line:
A valid antiforgery token was not provided with the request. Add an antiforgery token, or disable antiforgery validation for this endpoint.However, at this point I noticed the user cookie is set to browser already.
Program.cs:
// Add MudBlazor servicesbuilder.Services.AddMudServices();builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents();builder.Services.AddRazorPages();// Add services to Authbuilder.Services.AddCascadingAuthenticationState();builder.Services.AddAuthorization();builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.LoginPath = "/Account/Login"; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; });builder.Services.AddAntiforgery();builder.Services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();var app = builder.Build();// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts();}app.UseHttpsRedirection();app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseAntiforgery();app.MapRazorPages();app.MapStaticAssets();app.MapRazorComponents<App>() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode();app.Run();login.razor:
@page "/Account/Login"<MudPaper Class="mx-auto mt-12 pa-4" Elevation="4" Style="max-width:400px;"><MudText Typo="Typo.h5" Align="Align.Center" Class="mb-4">LogIn</MudText><EditForm Model=@Input OnValidSubmit="HandleValidSubmit" FormName="LoginForm"><DataAnnotationsValidator /><MudStaticTextField @bind-Value="@Input.UserName" name="Input.UserName" Label="User Name" For="@(() => Input.UserName)" FullWidth="true" Class="mb-4" Required="true" /><MudStaticTextField @bind-Value="@Input.Password" name="Input.Password" Label="Password" For="@(() => Input.Password)" Variant="Variant.Text" InputType="InputType.Password" FullWidth="true" Class="mb-4" Required="true" /><MudStaticSwitch Label="Remember Me" @bind-Value="Input.RememberMe" Color="Color.Primary" Class="mb-4" /><MudButton ButtonType="ButtonType.Submit" Color="Color.Primary" Variant="Variant.Filled" Class="mt-2" FullWidth="true"> LogIn</MudButton></EditForm> @if (!string.IsNullOrEmpty(errorMessage)) {<MudAlert Class="mt-3" Color="Color.Error">@errorMessage</MudAlert> }</MudPaper>@code { [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm(FormName = "LoginForm")] private InputModel Input { get; set; } = new InputModel(); private string errorMessage = string.Empty; private async Task HandleValidSubmit() { try { var claims = new List<Claim>{new Claim(ClaimTypes.Name, Input.UserName),new Claim(ClaimTypes.Role, "Admin")}; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); var authProperties = new AuthenticationProperties { IsPersistent = Input.RememberMe, ExpiresUtc = DateTimeOffset.UtcNow.Add( Input.RememberMe ? TimeSpan.FromDays(30) : TimeSpan.FromMinutes(30)), AllowRefresh = true }; await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, principal, authProperties); Navigation.NavigateTo("/", true); } catch (Exception ex) { errorMessage = "Login failed: " + ex.Message; } } public class InputModel { [Required(ErrorMessage = "UserName cannot be empty")] [StringLength(20, ErrorMessage = "1-20 length")] public string UserName { get; set; } = string.Empty; [Required(ErrorMessage = "Password cannot be empty")] [DataType(DataType.Password)] [StringLength(20, MinimumLength = 1, ErrorMessage = "1-20 length")] public string Password { get; set; } = string.Empty; public bool RememberMe { get; set; } = true; }}logout.razor:
@page "/Account/Logout"@using Microsoft.AspNetCore.Authentication@inject NavigationManager Navigation@code { [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; protected async override Task OnInitializedAsync() { base.OnInitialized(); await HttpContext.SignOutAsync(); Navigation.NavigateTo("/account/login", true); }}I tried to add <AntiforgeryToken \> to EditForm in login.razor like this:
<EditForm Model=@Input OnValidSubmit="HandleValidSubmit" FormName="LoginForm"><AntiforgeryToken />But it makes things worse, even for the first time login, it said: A valid antiforgery token was not provided with the request. Add an antiforgery token, or disable antiforgery validation for this endpoint.
I am totally new to both Blazor and Antiforgery, can anyone help me out with it? I am open to any kind of solutions, disable Antiforgery or work it out straightly.
UPDATE:I finally figured a way out, not really solve it, but at least bypass it, by removing try-catch clause in login function.
Replace:
private async Task SignIn() { try { # Some sign in logic... Navigation.NavigateTo("/", true); } catch (Exception ex) { errorMessage = "Login failed: " + ex.Message; } }with:
private async Task SignIn() { # Some sign in logic... Navigation.NavigateTo("/", true); }Then the AnitforgeryToken missing exception just gone!Still have no clue how this worked. But if you have same problem, it worth a try. I will let this one open for a while and wait for a better solution or explanation.