Cross-Site Request Forgery (CSRF) is one of those attacks that sounds theoretical until you see it in practice. A malicious site tricks a user's browser into making an authenticated request to your application. Because the browser automatically sends cookies, your application sees a perfectly valid session — and executes the request. The user never intended to make it.

How the Attack Works

Imagine a banking application where transferring money is a simple POST:

POST /transfer
Content-Type: application/x-www-form-urlencoded
Cookie: session=abc123

amount=1000&to=attacker-account

An attacker creates a page with a hidden form that auto-submits:

page.html
<form action="https://bank.example.com/transfer" method="POST">
    <input type="hidden" name="amount" value="1000" />
    <input type="hidden" name="to" value="attacker-account" />
</form>
<script>document.forms[0].submit();</script>

When a logged-in user visits this page, their browser sends the request complete with session cookies. The bank processes it as a legitimate transfer.

ASP.NET Core's Anti-Forgery System

ASP.NET Core prevents CSRF using the synchroniser token pattern. It generates two tokens: one stored in a cookie and one embedded in the form. On submission, both must match. An attacker can't read the form token from another domain, so they can't forge a valid request.

In Razor Pages and MVC, anti-forgery is enabled by default for form posts. The @Html.AntiForgeryToken() tag helper or the [ValidateAntiForgeryToken] attribute handles it:

Pages/Transfer.cshtml.cs
public class TransferModel : PageModel
{
    public IActionResult OnPost(TransferRequest request)
    {
        // Anti-forgery token is validated automatically
        return RedirectToPage("Confirmation");
    }
}

In MVC controllers, apply the attribute:

Example.cs
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Transfer(TransferRequest request)
{
    // Process transfer
    return RedirectToAction("Confirmation");
}

Or apply it globally so you don't forget:

Program.cs
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

AutoValidateAntiforgeryTokenAttribute is smarter than ValidateAntiForgeryToken — it only validates on unsafe HTTP methods (POST, PUT, DELETE) and skips safe methods (GET, HEAD).

Configuring Anti-Forgery Options

Program.cs
builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-XSRF-TOKEN";
    options.Cookie.Name = "XSRF-COOKIE";
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Strict;
});

Setting SameSite to Strict provides additional protection — the browser won't send the cookie at all on cross-site requests, making CSRF impossible for browsers that support it. However, this breaks legitimate cross-site navigation flows, so Lax is often a better choice.

CSRF Protection for APIs

Traditional CSRF protection relies on cookies and form tokens. APIs typically use bearer tokens (JWT or similar), which are not sent automatically by the browser. This makes them inherently resistant to CSRF — an attacker can't force the browser to include a bearer token.

If your API does use cookie-based authentication (common in Blazor Server or SPAs with BFF patterns), you need CSRF protection:

Program.cs
app.MapPost("/api/transfer", async (TransferRequest request,
    IAntiforgery antiforgery, HttpContext context) =>
{
    await antiforgery.ValidateRequestAsync(context);

    // Process the transfer
    return Results.Ok();
});

For SPAs, a common pattern is to expose an endpoint that provides the anti-forgery token:

Program.cs
app.MapGet("/api/antiforgery/token", (IAntiforgery antiforgery,
    HttpContext context) =>
{
    var tokens = antiforgery.GetAndStoreTokens(context);
    return Results.Ok(new { token = tokens.RequestToken });
});

The SPA fetches this token and includes it in subsequent requests as a header.

SameSite Cookies: A Defence in Depth

Modern browsers support the SameSite cookie attribute, which provides strong CSRF protection at the browser level:

ASP.NET Core defaults to Lax for most cookies, which prevents the most common CSRF vectors whilst allowing normal link navigation.

When You Don't Need CSRF Protection

You can skip CSRF tokens when:

Wrapping Up

CSRF protection in ASP.NET Core is largely automatic for server-rendered applications. The framework generates and validates tokens without much intervention. For SPAs and APIs using cookie-based auth, you'll need to wire up token distribution manually. The combination of anti-forgery tokens and SameSite cookies provides robust defence against request forgery attacks.