Tutorials ASP.NET Core MVC Mastery
Anti-forgery
On this page
Anti-Forgery (CSRF) Protection — Securing Data Mutation
Cross-Site Request Forgery (CSRF) is a devastating attack where a malicious website forces an authenticated user's browser to execute unwanted actions (like transferring funds or changing passwords) on a trusted site. ASP.NET Core MVC deeply integrates the Anti-Forgery Token pattern to decisively block these attacks.
1. WHAT is CSRF and How Do Tokens Work?
A CSRF attack relies on a browser's automated behavior: if you are logged into bank.com, your browser stores a session cookie. If you visit hacker.com, the hacker can create an invisible form pointing to bank.com/transfer. When hacker.com submits that form, your browser automatically attaches the bank.com session cookie. The server thinks you authorized the transfer.
The Synchronizer Token Pattern
- When rendering a form, ASP.NET generates a unique, cryptographically secure Token.
- It places this token in a hidden HTML
<input>field AND in an encrypted HttpOnly Cookie. - When the form is POSTed back, ASP.NET compares the Token in the form data to the Token in the cookie.
- The Hacker's Failing: The hacker on
hacker.comcannot read the bank's cookie (due to Same-Origin Policy) and therefore cannot know the correct token to put in their fake form. The server blocks the request with a400 Bad Request.
2. REAL-TIME PRODUCTION EXAMPLES
Example 1: Standard Razor View Forms
In modern ASP.NET Core, if you use the Form Tag Helper (<form asp-action="...">), the Anti-Forgery token is injected automatically. You do nothing.
<!-- The Tag Helper automatically generates the hidden token input -->
<form asp-controller="Transfer" asp-action="Execute" method="post">
<input type="text" name="amount" />
<button type="submit">Transfer</button>
</form>
<!-- Renders as: -->
<form action="/Transfer/Execute" method="post">
<input name="amount" type="text" />
<button type="submit">Transfer</button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8J..." />
</form>
// The Controller
public class TransferController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken] // CRITICAL: This enforces validation
public IActionResult Execute(decimal amount)
{
// Safe from CSRF!
return Ok();
}
}
Example 2: AutoValidateAntiforgeryToken (Enterprise Default)
Relying on developers to remember [ValidateAntiForgeryToken] on every POST/PUT/DELETE is a recipe for disaster. Apply it globally.
// Program.cs (.NET 6+)
builder.Services.AddControllersWithViews(options =>
{
// Automatically validates tokens for ALL mutable requests (POST, PUT, DELETE, PATCH)
// Ignores safe GET, HEAD, TRACE, OPTIONS requests.
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
Now you explicitly opt-OUT via [IgnoreAntiforgeryToken] only for external webhooks or public APIs that use JWTs instead of cookies.
Example 3: AJAX & Single Page Applications (React/Vue/jQuery)
If you POST data via JavaScript fetch() or jQuery rather than standard HTML forms, the hidden input isn't automatically sent. You must extract it and send it as an HTTP Header.
<!-- Ensure the token is on the page -->
@Html.AntiForgeryToken()
<script>
async function doSecurePostData() {
// Read the token from the hidden input generated above
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
await fetch('/api/secure-action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// ASP.NET Core automatically looks for this specific header name for AJAX
'RequestVerificationToken': token
},
body: JSON.stringify({ amount: 100 })
});
}
</script>
3. The IAntiforgery API (Advanced SPAs)
For strictly separated SPAs (Angular/React) that don't use Razor Views, you can generate and send the token to the client via an endpoint or a cookie.
public class AuthController : Controller
{
private readonly IAntiforgery _antiforgery;
public AuthController(IAntiforgery antiforgery) => _antiforgery = antiforgery;
[HttpGet("api/get-csrf-token")]
public IActionResult GetToken()
{
// Generate the token set
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
// Tell the SPA what the token is, so it can include it in future POST headers
return Ok(new { token = tokens.RequestToken });
}
}
4. Best Practices
✅ DO
- Apply
AutoValidateAntiforgeryTokenAttributeglobally inProgram.cs. - Ensure all state-mutating actions (create, edit, delete, fund transfers) are restricted to POST/PUT/DELETE.
- Rely on Tag Helpers for forms—they handle token injection automatically.
❌ DON'T
- Never use HTTP GET to mutate state (e.g.,
/Home/DeleteUser/5). Anti-forgery tokens only protect POST/PUT/DELETE requests. A GET link can be maliciously embedded in an image tag (<img src="/Home/DeleteUser/5">), executing immediately. - Avoid disabling tokens globally just because an API is failing. Instead, use
[IgnoreAntiforgeryToken]solely on the endpoints acting as public webhooks.
5. Interview Mastery
Q: "If my application only uses JWT (JSON Web Tokens) for authentication, and stores them in LocalStorage instead of Cookies, do I still need Anti-Forgery Tokens?"
Architect Answer: "No. CSRF attacks strictly rely on the browser's automated behavior of blindly attaching Cookies to every out-bound request to that domain. If your app uses LocalStorage and JavaScript explicitly attaches an Authorization: Bearer <JWT> header to API calls, a hacker's forged form cannot access your LocalStorage, so it cannot attach the JWT. However, storing JWTs in LocalStorage opens you up to XSS (Cross-Site Scripting). For maximum enterprise security, the current best practice is the 'BFF' (Backend For Frontend) pattern: store JWTs in secure, server-side HttpOnly cookies, and thus, re-introduce Anti-Forgery tokens to protect those cookies."