I created a straight-from-the-template Blazor 9 WASM Web App with Authentication type set to Individual Accounts, and Global Webassembly interaction.
The only change I made was to swap in Microsoft.EntityFrameworkCore.InMemory
.
When I run the app, I can register and log in fine.
However, when I hit the "Logout" link, it navigates to "Account/Logout" which has the following exception:
AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken".
BadHttpRequestException: Invalid anti-forgery token found when reading parameter "string returnUrl" from the request body as form.
Does anyone know what the issue is?
I created a straight-from-the-template Blazor 9 WASM Web App with Authentication type set to Individual Accounts, and Global Webassembly interaction.
The only change I made was to swap in Microsoft.EntityFrameworkCore.InMemory
.
When I run the app, I can register and log in fine.
However, when I hit the "Logout" link, it navigates to "Account/Logout" which has the following exception:
AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken".
BadHttpRequestException: Invalid anti-forgery token found when reading parameter "string returnUrl" from the request body as form.
Does anyone know what the issue is?
Share Improve this question edited 23 hours ago Ruikai Feng 11.7k1 gold badge7 silver badges16 bronze badges asked Feb 7 at 12:57 MikeTMikeT 2,6631 gold badge29 silver badges40 bronze badges1 Answer
Reset to default 0I reproduced the issue on myside:
The issue has nothing to do with Microsoft.EntityFrameworkCore.InMemory
,the error occoured due to <AntiforgeryToken />
component failed generating antiforgy token during interactive rendering,for more details, you could read this document,this issue has been reported here and would be fixed in .net 10
I tried to inject AntiforgeryStateProvider
into NavMenu
component:
@inject AntiforgeryStateProvider Antiforgy
and call var antiforgytoken = Antiforgy.GetAntiforgeryToken();
in OnInitialized()
method
antiforgytoken
get null value on second call(interactive rendering):
A workaround to avoid the error:
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();
}
}
Register it in program.cs after basic services of Blazor:
builder.Services.AddScoped<AntiforgeryStateProvider, WorkaroundEndpointAntiforgeryStateProvider>();