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.
Use caution when setting this value. Using it improperly can open security vulnerabilities in the application.
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.
@using (Html.BeginForm("Manage", "Account")) {
@Html.AntiForgeryToken()
<!-- ... -->
}
OR
<form>
@Html.AntiForgeryToken()
<!-- ... -->
</form>
The target action method:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult ActionMethod(ModelObject model)
{
// ...
}
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;
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());
//...
}
}
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.
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