100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET Framework

Anti-Forgery Tokens

How ASP.NET MVC prevents Cross-Site Request Forgery (CSRF) using the @Html.AntiForgeryToken helper and the ValidateAntiForgeryToken filter.

SecurityIntermediate8 min readJul 10, 2026
Analogies

What Cross-Site Request Forgery Attacks Exploit

CSRF attacks exploit the fact that browsers automatically attach a site's cookies to any request sent to that site, even one triggered by a malicious page hosted elsewhere. If a logged-in bank customer visits an attacker's page containing a hidden auto-submitting form pointed at yourbank.com/Transfer, the browser happily includes the victim's valid authentication cookie, and the server -- seeing a properly authenticated request -- has no way to know the submission wasn't intentional unless it checks for something the attacker's page couldn't have obtained: a per-session anti-forgery token.

🏏

Cricket analogy: It's like a fan's stadium wristband (cookie) being scanned automatically at the members' gate even if someone else physically walked them there without their knowledge -- the gate trusts the wristband, not the intent behind the walk.

How @Html.AntiForgeryToken Defeats the Attack

Adding @Html.AntiForgeryToken() inside a Razor <form> renders a hidden input (__RequestVerificationToken) containing a random, unpredictable value that is also mirrored into a cookie sent to the browser separately from the session cookie. Because the attacker's malicious page has no way to read the victim's cookies or embed the correct hidden field value -- same-origin policy blocks cross-site cookie reads -- any forged form submission will either omit the token entirely or send a mismatched value, and the server rejects it before the action runs.

🏏

Cricket analogy: It's like a franchise requiring both a player's membership wristband AND a unique one-time code texted to their registered phone before allowing a locker-room-only door to open, a code an outsider could never intercept.

Wiring Up Server-Side Validation

The token by itself does nothing unless the receiving action is decorated with [ValidateAntiForgeryToken], which compares the cookie-based token against the form-field token and rejects the request with a 400 if they don't match or either is missing. Best practice is to apply this to every state-changing HTTP POST/PUT/DELETE action -- never to GET actions, since GET requests shouldn't mutate state in the first place -- and, for AJAX-heavy SPAs layered on MVC, to manually read the token from the DOM and attach it as a request header, since jQuery's $.ajax won't include hidden form fields automatically the way a normal form POST does.

🏏

Cricket analogy: It's like a stadium's turnstile scanner (the filter) actually rejecting entry if the wristband and the printed ticket number don't match, rather than just displaying both without comparing them.

csharp
@* View: Views/Account/ChangePassword.cshtml *@
@using (Html.BeginForm("ChangePassword", "Account", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.PasswordFor(m => m.NewPassword)
    <button type="submit">Change Password</button>
}

// Controller
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult ChangePassword(ChangePasswordViewModel model)
{
    if (!ModelState.IsValid) return View(model);

    var result = UserManager.ChangePassword(
        User.Identity.GetUserId(), model.OldPassword, model.NewPassword);

    return result.Succeeded
        ? RedirectToAction("Index", "Manage")
        : View(model);
}

// Manually attaching the token for an AJAX request
var token = $('input[name="__RequestVerificationToken"]').val();
$.ajax({
    url: '/Account/ChangePassword',
    type: 'POST',
    headers: { 'RequestVerificationToken': token },
    data: { newPassword: $('#newPassword').val() }
});

GET requests should be idempotent and never change server state, so they don't need anti-forgery tokens at all -- the real fix for a state-changing GET link is to convert it into a POST form, not to bolt a token onto it.

Forgetting [ValidateAntiForgeryToken] on even one state-changing POST action leaves that specific endpoint fully exposed to CSRF, regardless of how well every other action in the controller is protected -- the token check is per-action, not automatically inherited site-wide.

  • CSRF exploits the browser's automatic cookie attachment to trick a server into processing an unintended request as if the victim intended it.
  • @Html.AntiForgeryToken() renders a hidden field and a matching cookie with an unpredictable, session-specific value.
  • [ValidateAntiForgeryToken] on the receiving action compares the form field and cookie values, rejecting mismatches with a 400.
  • Same-origin policy prevents an attacker's page from reading the victim's cookie or reproducing the matching hidden field value.
  • Protection should be applied to every state-changing POST/PUT/DELETE action, never relied on for GET requests.
  • AJAX requests must manually read and attach the token as a header since it isn't part of a normal form POST body automatically.
  • The check is per-action; missing it on a single endpoint leaves that endpoint exposed even if others are protected.

Practice what you learned

Was this page helpful?

Topics covered

#NETFramework#ASPNETMVCStudyNotes#MicrosoftTechnologies#AntiForgeryTokens#Anti#Forgery#Tokens#Cross#StudyNotes#SkillVeris