Cross-site request forgery (CSRF) protection prevents a malicious site from causing a browser to submit an authenticated request using the victim’s session cookie. ASP.NET Core forms can emit a token cookie and a matching hidden form field; the server validates both before accepting a protected request. section 27.8
Forms generated with routing attributes such as asp-controller, asp-action, or asp-page receive the anti-forgery fields automatically when the form has no action attribute. Controllers otherwise accept POST requests without tokens, so apply AutoValidateAntiforgeryToken to validate every method except GET, HEAD, OPTIONS, and TRACE. section 27.8 section 27.8.1
Control validation according to the request surface:
- Use ValidateAntiForgeryToken to enforce validation even for methods that normally do not require it. section 27.8.1
- Use IgnoreAntiforgeryToken only when an action or handler deliberately needs validation suppressed. section 27.8.1 section 27.8.2
- Razor Pages enable anti-forgery validation by default; removing an IgnoreAntiforgeryToken override restores the protection. section 27.8.2
Testing a rejected form
Open the form, use browser developer tools to remove or alter its hidden token, then submit it. The request should fail because the required token data no longer matches. section 27.8.1
Listing 27.31 Configuring the anti-forgery token in the WebApp/Program.cs file
using Microsoft.EntityFrameworkCore;
using WebApp.Models;
using Microsoft.AspNetCore.Antiforgery;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DataContext>(opts => {
opts.UseSqlServer(builder.Configuration[
"ConnectionStrings:ProductConnection"]);
opts.EnableSensitiveDataLogging(true);
});
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddSingleton<CitiesData>();
builder.Services.Configure<AntiforgeryOptions>(opts => {
opts.HeaderName = "X-XSRF-TOKEN";
});
var app = builder.Build();
app.UseStaticFiles();
IAntiforgery antiforgery
= app.Services.GetRequiredService<IAntiforgery>();
app.Use(async (context, next) => {
if (!context.Request.Path.StartsWithSegments("/api")) {
string? token =
antiforgery.GetAndStoreTokens(context).RequestToken;
if (token != null) {
context.Response.Cookies.Append("XSRF-TOKEN",
token,
new CookieOptions { HttpOnly = false });
}
}
await next();
});
app.MapControllerRoute("forms",
"controllers/{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
var context = app.Services.CreateScope().ServiceProvider
.GetRequiredService<DataContext>();
SeedData.SeedDatabase(context);
app.Run();
Configure a JavaScript-readable XSRF-TOKEN cookie and accept the matching X-XSRF-TOKEN header through AntiforgeryOptions and middleware. section 27.8.3
JavaScript clients cannot rely on a hidden HTML input when the server returns data rather than rendered forms. Expose a readable cookie, configure the accepted header name, and copy the cookie value into that header on POST requests; frameworks such as Angular can automate this convention. section 27.8.3
