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.
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.
<input> field AND in an encrypted HttpOnly Cookie.hacker.com cannot 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 a 400 Bad Request.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();
}
}
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.
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>
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 });
}
}
AutoValidateAntiforgeryTokenAttribute globally in Program.cs./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.[IgnoreAntiforgeryToken] solely on the endpoints acting as public webhooks.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."