-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Logout doesn't work in Blazor Web Application with global WASM interactivity (AntiforgeryValidationException) #58822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The problem is that html form does not contain __RequestVerificationToken hidden field. This bug is related to this issue #54533 and it looks it is NOT fixed. Original issue was reported by @SteveSandersonMS. Pinging @javiercn again because he was working on the fix in .NET 9 RC1. |
I can also reproduce the issue in several of our projects ( The problem is that the If I add However, it somehow doesn't make its way into the prerendered state:
|
@javiercn I think I found the cause of the bug. It's not the
A workaround could involve influencing the order of registrations in The ultimate fix should be either:
If we agree on the solution, I can prepare a PR to address this. cc @jirikanda |
Workaround
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace Havit.NewProjectTemplate.Web.Server.Infrastructure.Antiforgery;
// Replicates EndpointAntiforgeryStateProvider (incl. base DefaultAntiforgeryStateProvider) and adds a workaround for the issue with the ResourceCollectionProvider
// https://github.com/dotnet/aspnetcore/issues/58822
public class WorkaroundEndpointAntiforgeryStateProvider : AntiforgeryStateProvider, IDisposable
{
private const string PersistenceKey = $"__internal__{nameof(AntiforgeryRequestToken)}";
private readonly PersistingComponentStateSubscription _subscription;
private readonly PersistingComponentStateSubscription _subscriptionDummy;
private readonly AntiforgeryRequestToken _currentToken;
private readonly IAntiforgery _antiforgery;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly PersistentComponentState _state;
public WorkaroundEndpointAntiforgeryStateProvider(
IAntiforgery antiforgery,
IHttpContextAccessor httpContextAccessor,
PersistentComponentState state)
{
// This is a dummy subscription that does nothing. It's only here as the previous ResourceCollectionProvider
// persising callback disposes its subscription, which modifies the ComponentStatePersistenceManager._registeredCallbacks
// collection in while looping and causing next callback to be skipped.
_subscriptionDummy = state.RegisterOnPersisting(() => Task.CompletedTask, RenderMode.InteractiveWebAssembly);
// Automatically flow the Request token to server/wasm through
// persistent component state. This guarantees that the antiforgery
// token is available on the interactive components, even when they
// don't have access to the request.
_subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);
state.TryTakeFromJson(PersistenceKey, out _currentToken);
_antiforgery = antiforgery;
_httpContextAccessor = httpContextAccessor;
_state = state;
}
private Task OnPersistingAsync()
{
_state.PersistAsJson(PersistenceKey, GetAntiforgeryToken());
return Task.CompletedTask;
}
public override AntiforgeryRequestToken GetAntiforgeryToken()
{
if (_httpContextAccessor.HttpContext == null)
{
// We're in an interactive context. Use the token persisted during static rendering.
return _currentToken;
}
// We already have a callback setup to generate the token when the response starts if needed.
// If we need the tokens before we start streaming the response, we'll generate and store them;
// otherwise we'll just retrieve them.
// In case there are no tokens available, we are going to return null and no-op.
var tokens = !_httpContextAccessor.HttpContext.Response.HasStarted ? _antiforgery.GetAndStoreTokens(_httpContextAccessor.HttpContext) : _antiforgery.GetTokens(_httpContextAccessor.HttpContext);
if (tokens.RequestToken is null)
{
return null;
}
return new AntiforgeryRequestToken(tokens.RequestToken, tokens.FormFieldName);
}
/// <inheritdoc />
public void Dispose()
{
_subscriptionDummy.Dispose();
_subscription.Dispose();
}
}
app.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
app.Services.AddScoped<AntiforgeryStateProvider, WorkaroundEndpointAntiforgeryStateProvider>(); UPDATE: aspnetcore/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs Lines 100 to 111 in cb97c65
|
Could someone correct this bug? :( |
@javiercn please help :( |
The workaround provided by @hakenr is working perfectly! |
I've spent at least a full day troubleshooting this while trying to update to .NET9 which breaks my app here and there. For some reason I did not have the luxury or receiving the nice AntiforgeryValidationException the OP was getting - all I got was 400 response and no message at all in the console, having me chasing a routing red herring... My investigations lead me to the same conclusion as @hakenr (kudos for writing it up). But I went for a simpler workaround: Alternative workaroundA simple workaround that does not replicate the underlying services, which also hopefully does not have any adverse effects should these antiforgery bugs be solved once and for all.
using Microsoft.AspNetCore.Components.Web;
namespace Microsoft.AspNetCore.Components.Forms;
public sealed class PersistAntiforgeryState : ComponentBase, IDisposable
{
private const string PersistenceKey = $"__internal__{nameof(AntiforgeryRequestToken)}";
private PersistingComponentStateSubscription _subscription;
[Inject] PersistentComponentState PersistentState { get; set; }
[Inject] AntiforgeryStateProvider AntiforgeryProvider { get; set; }
protected override void OnInitialized()
{
_subscription = PersistentState.RegisterOnPersisting(() =>
{
PersistentState.PersistAsJson(PersistenceKey, AntiforgeryProvider.GetAntiforgeryToken());
return Task.CompletedTask;
}, RenderMode.InteractiveAuto);
}
public void Dispose() => _subscription.Dispose();
}
<PersistAntiforgeryState /> @* Workaround for AntiforgeryToken bug like https://github.com/dotnet/aspnetcore/issues/58822 *@
<!DOCTYPE html>
<html lang="en">
<head>
... |
Workaround proposed by @MichelJansson works perfectly. |
Workaround proposed by @MichelJansson works perfectly for me.. |
Is there an existing issue for this?
Describe the bug
AntiforgeryValidationException after clicking logout when Blazor WASM interactive page is displayed in application with global interactivity.
Expected Behavior
Logout should work without exceptions.
Steps To Reproduce
dotnet new blazor -n LogoutTest --interactivity WebAssembly --auth Individual --all-interactive True
Exceptions (if any)
.NET Version
9.0.100-rc.2.24474.11
Anything else?
Probably related issue #56687
Pinging @javiercn because he was active in related issue.
The text was updated successfully, but these errors were encountered: