Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

Antiforgery Token not generated in Blazor server web app

$
0
0

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.


Viewing all articles
Browse latest Browse all 4839

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>