Html.AntiForgeryToken
Introduction#
The anti-forgery token can be used to help protect your application against cross-site request forgery. To use this feature, call the AntiForgeryToken method from a form and add the ValidateAntiForgeryTokenAttribute attribute to the action method that you want to protect.
Generates a hidden form field (anti-forgery token) that is validated when the form is submitted.
Syntax#
- @Html.AntiForgeryToken()
Remarks#
When submitting an ajax request with CSRF token (__RequestVerificationToken
) make sure that content type is not set to application/json
. If you are using jQuery it automatically sets the content type to application/x-www-form-urlencoded
which is then recognised by ASP.NET MVC.
Caution
Use caution when setting this value. Using it improperly can open security vulnerabilities in the application.
Basic usage
The @Html.AntiForgeryToken()
helper method protects against cross-site request forgery (or CSRF) attacks.
It can be used by simply using the Html.AntiForgeryToken()
helper within one of your existing forms and decorating its corresponding Controller Action with the [ValidateAntiForgeryToken]
attribute.
Razor (YourView.cshtml)
@using (Html.BeginForm("Manage", "Account")) {
@Html.AntiForgeryToken()
<!-- ... -->
}
OR
<form>
@Html.AntiForgeryToken()
<!-- ... -->
</form>
Controller (YourController.cs)
The target action method:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult ActionMethod(ModelObject model)
{
// ...
}
Disable Identity Heuristic Check
Often times you will see an exception
Anti forgery token is meant for user "" but the current user is "username"
This is because the Anti-Forgery token is also linked to the current logged-in user. This error appears when a user logs in but their token is still linked to being an anonymous user for the site.
There are a few ways to fix this behavior, but if you would rather not have CSRF tokens linked to the logged-in state of a user you may disable this feature.
Put this line in your Global.asax
file or similar application startup logic.
AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
Validating All Posts
Due to the vulnerability caused by CSRF, it is generally considered a good practice to check for an AntiForgeryToken on all HttpPosts unless there is a good reason to not do it (some technical issue with the post, there is another authentication mechanism and/or the post does not mutate state like saving to a db or file). To ensure that you don’t forget, you can add a special GlobalActionFilter that automatically checks all HttpPosts unless the action is decorated with a special “ignore” attribute.
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext.HttpContext.Request;
// Only validate POSTs
if (request.HttpMethod == WebRequestMethods.Http.Post)
{
bool skipCheck = filterContext.ActionDescriptor.IsDefined(typeof(DontCheckForAntiForgeryTokenAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(DontCheckForAntiForgeryTokenAttribute), true);
if (skipCheck)
return;
// Ajax POSTs and normal form posts have to be treated differently when it comes
// to validating the AntiForgeryToken
if (request.IsAjaxRequest())
{
var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];
var cookieValue = antiForgeryCookie != null
? antiForgeryCookie.Value
: null;
AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
}
else
{
new ValidateAntiForgeryTokenAttribute()
.OnAuthorization(filterContext);
}
}
}
}
/// <summary>
/// this should ONLY be used on POSTS that DO NOT MUTATE STATE
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class DontCheckForAntiForgeryTokenAttribute : Attribute { }
To make sure it gets checked on all requests, just add it to your Global Action Filters
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//...
filters.Add(new ValidateAntiForgeryTokenOnAllPosts());
//...
}
}
Advance usage: Apply default Antiforgery filter for every POST
We may forget to apply the Antiforgery attribute
for each POST
request so we should make it by default. This sample will make sure Antiforgery filter
will always be applied to every POST
request.
Firstly create new AntiForgeryTokenFilter
filter:
//This will add ValidateAntiForgeryToken Attribute to all HttpPost action methods
public class AntiForgeryTokenFilter : IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
List<Filter> result = new List<Filter>();
string incomingVerb = controllerContext.HttpContext.Request.HttpMethod;
if (String.Equals(incomingVerb, "POST", StringComparison.OrdinalIgnoreCase))
{
result.Add(new Filter(new ValidateAntiForgeryTokenAttribute(), FilterScope.Global, null));
}
return result;
}
}
Then register this custom filter to MVC, Application_Start:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//Cactch generic error
filters.Add(new HandleErrorAttribute());
//Anti forgery token hack for every post request
FilterProviders.Providers.Add(new AntiForgeryTokenFilter());
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
So now all your POST
requests are protected by default using Antiforgery attributes so we are no longer need to have [ValidateAntiForgeryToken]
attribute on each POST method.
Using AntiForgeryToken with Jquery Ajax Request
First off you create the form
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
}
Action Method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(FormViewModel formData)
{
// ...
}
Script
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script>
var formData = new FormData($('form')[0]);
$.ajax({
method: "POST",
url: "/demo/test",
data: formData ,
success: function (data) {
console.log(data);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
}
})
</script>
Make sure contentType isn’t set to anything apart from application/x-www-form-urlencoded
and if its not specified Jquery defaults to application/x-www-form-urlencoded