A valid antiforgery token was not provided with the request. Add an antiforgery token, or disable antiforgery validation for this endpoint. is the 400 answer a Blazor Web App gives when a plain HTML <form> posts to a static server-rendered page without a token field. Put the AntiforgeryToken component inside the form and the same post is accepted.

The fix is one line: <AntiforgeryToken /> between the form tags. If you use EditForm instead of a raw form, you do not need it; in the test below EditForm wrote the token field on its own.

The error

HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=utf-8
...
blazor-enhanced-nav: allow

A valid antiforgery token was not provided with the request. Add an antiforgery token, or disable antiforgery validation for this endpoint.

That is the whole response body. In this run the app's console log at the default level had no line about it: no warning, no exception, so the browser (or curl) is the only place you see the message.

Why it happens

The Blazor Web App template's Program.cs calls app.UseAntiforgery(); before app.MapRazorComponents<App>();, so a POST to a Razor component endpoint must carry a valid __RequestVerificationToken field together with the antiforgery cookie the page set on the GET. A hand-written form that has @formname and @onsubmit but no token field sends only its own inputs, and the request is refused with 400 before the handler runs.

The page that triggered it:

@page "/subscribe"

<h1>Subscribe</h1>

<form method="post" @formname="subscribe" @onsubmit="Save">
    <input name="Email" value="maria.garcia@example.test" />
    <button type="submit">Subscribe</button>
</form>

<p>@message</p>

@code {
    string message = "";
    void Save() => message = "Saved";
}

The fix

<form method="post" @formname="subscribe" @onsubmit="Save">
    <input name="Email" value="maria.garcia@example.test" />
    <AntiforgeryToken />
    <button type="submit">Subscribe</button>
</form>

After the change the GET of /subscribe contained a hidden __RequestVerificationToken input. Posting the form again with that field and the cookie from the GET returned HTTP/1.1 200 OK and the page showed Saved, so the handler ran. A test that posts with curl must do the same: GET the page with a cookie jar, read the token from the HTML and send both back.

The message also offers to disable validation for the endpoint. That removes the protection against cross-site form posts, so add the token unless the endpoint really takes posts from another site.

How it was reproduced

A fresh project from dotnet new blazor -o Af --interactivity None on .NET SDK 10.0.403 with the ASP.NET Core 10.0.14 runtime, one page with the raw form above, run with dotnet run --no-build --urls http://127.0.0.1:15751. The POST was curl.exe -s -i -X POST -d '_handler=subscribe&Email=x' http://127.0.0.1:15751/subscribe. The fix was confirmed with a cookie jar and the token read from the page.

Frequently asked

How do I add an antiforgery token to a Blazor form?
Put the AntiforgeryToken component inside the form element. It renders a hidden __RequestVerificationToken input, and the post is accepted together with the antiforgery cookie the page set.
Does EditForm need AntiforgeryToken in Blazor?
In the test on .NET 10 it did not: EditForm wrote the hidden __RequestVerificationToken input into the rendered form by itself. A raw form element needs the component.
Why does my curl POST to a Blazor page return 400?
The request has no antiforgery token. GET the page first with a cookie jar, read the __RequestVerificationToken value from the HTML and send it with the form fields and the cookie.

More decoded errors in the Fixes category. If the same form fails with a missing form name instead, see The POST request does not specify which form is being submitted.