ASP.NET Core: Cross-Site Request Forgery (CSRF)
Cross-Site Request Forgery (CSRF) is an attack where a malicious website tricks an authenticated user into performing an unwanted action on a trusted site where the user is currently logged in.
Because browsers automatically send cookies (including session/authentication cookies) with every request to a specific domain, the server cannot distinguish if the request was initiated by the user willingly or forced by a malicious script.
Real-World Analogy
Imagine you are a bank teller. You have a rule: "If I see a valid ID badge, I process the transfer."
A customer (User) walks up to your counter with their ID badge visible (Logged in).
A stranger (Attacker) stands behind them and whispers, "Transfer $1,000 to the stranger," into the customer's ear.
The customer inadvertently repeats it.
You process the transfer because the ID badge is valid. You didn't verify who initiated the command, only that the person holding the ID badge sent it.
How the Attack Works (Step-by-Step)
Login: The user logs into
legit-bank.com. The bank issues an Authentication Cookie to the user's browser.Trap: Without logging out, the user visits
evil-site.com(perhaps via a phishing email).The Forge:
evil-site.comcontains a hidden script or form that automatically submits a POST request tolegit-bank.com/transfer?amount=1000&to=Attacker.Execution: The browser sees the request is for
legit-bank.comand automatically attaches the Authentication Cookie.Failure: The bank server receives the request, sees the valid cookie, and processes the transfer, thinking the user intended to do it.
How to Prevent CSRF in .NET Core
.NET Core uses the Synchronizer Token Pattern to prevent this.
The Solution Mechanism
The Token Pair: When the server renders a form, it generates two tokens:
Cookie Token: Sent as an HTTP-only cookie.
Request Token: Placed as a hidden field in the HTML form.
The Verification: When the user submits the form, the server checks if both tokens match.
The Defense: The malicious site cannot read the Cookie Token (due to same-origin policy) and therefore cannot fabricate the matching Request Token for the form. If the tokens are missing or don't match, .NET Core rejects the request.
Implementation in .NET Core (MVC & Razor Pages)
1. Service Configuration (Program.cs)
In .NET Core 2.0+, this is enabled by default. However, you can customize it in your startup configuration if needed.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// Optional: Customize Antiforgery options
builder.Services.AddAntiforgery(options =>
{
options.FormFieldName = "__RequestVerificationToken";
options.HeaderName = "X-CSRF-TOKEN";
options.SuppressXFrameOptionsHeader = false;
});
2. The View (Front-end)
If you use the standard <form> tag helper, .NET Core automatically injects the hidden token field for you.
<form asp-controller="Account" asp-action="Transfer" method="post">
<button type="submit">Transfer</button>
</form>
<form action="/Account/Transfer" method="post">
@Html.AntiForgeryToken()
<button type="submit">Transfer</button>
</form>
3. The Controller (Back-end)
You must decorate your Actions (or the entire Controller) with the [ValidateAntiForgeryToken] attribute. This forces the server to check for the token before running the code.
[HttpPost]
[ValidateAntiForgeryToken] // <--- Crucial line
public IActionResult Transfer(TransferModel model)
{
// If the token is invalid, the request never reaches this line.
// Logic to transfer money
return View();
}
Advanced: Global & API Protection
Option A: Global Filter (Recommended)
Instead of adding the attribute to every single POST method, you can apply it globally. This is safer as you can't "forget" to add it.
builder.Services.AddControllersWithViews(options =>
{
// Applies validation to all unsafe HTTP methods (POST, PUT, DELETE)
// automatically ignores GET, HEAD, OPTIONS, TRACE
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
Note: If you do this, you can use [IgnoreAntiforgeryToken] on specific endpoints that don't need protection (like public hooks).
Option B: AJAX / SPAs (React, Angular, Vue)
Single Page Applications don't use traditional HTML forms. For these, you must send the token in the Request Header.
Server Side: Configure the header name.
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");Client Side (JavaScript): Read the token from the cookie or a hidden input and send it in the header.
fetch('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': document.getElementsByName('__RequestVerificationToken')[0].value }, body: JSON.stringify({ amount: 1000 }) });
Summary Checklist
Action: Add
[ValidateAntiForgeryToken](or[AutoValidateAntiforgeryToken]) to your POST/PUT/DELETE controllers.View: Ensure
<form>tags are usingasp-actiontag helpers OR include@Html.AntiForgeryToken().Verification: Try to make a POST request using Postman to your endpoint without the token. It should return 400 Bad Request.