CSRF and AntiForgeryToken
Cross Site Request Forgery also known as CSRF (XSRF) is a widely exploited website vulnerability. In a CSRF attack, a malicious site instructs a victim's browser
to send a request to an honest site, as if request were part of the victim's interaction with the honest site, leveraging the victim's network connectivity
and the browser's state, such as cookies, to disrupt the integrity of the victim's session with the honest site. One of the popular technique to prevent CSRF attack
is by using security tokens (from here).
ASP.NET MVC suports prevention against CSRF through the AntiForgeryToken HTML helper and ValidateAntiForgeryToken filter. The AntiForgeryToken is supported
only for the POST requests and not for GET and this makes sense because the GET operation has to used only for safe operations (as per HTTP spec.).
In some applications we need all the POST operations should be validated for the anti-forgery token and in those cases instead of decorating all the POST actions in the
application with the ValidateAntiForgeryTokenAttribute we can create a custom authorization filter and apply it globally, that's what we are going to see in this article.
We will also see how to create an HTML helper that renders a form along with the hidden field that contains the security token.
What the AntiForgeryToken helper does?
The Html.AntiForgeryToken() creates a security token and sets it to a hidden field along with that it also sets the token to a httponly cookie.
@using(Html.BeginForm())
{
<p>
@@Html.AntiForgeryToken()
</p>
}
Hidden field
<form ..>
...
<input type="hidden"
name="__RequestVerificationToken">
value="d1Hh28W3uTpdZcEG0VhEkYg7D5XqFM9Sm4iA2e/cXgrOIIpPDENi1lVBg6mYLBAAoGk0q5RA/EPE2o6W5VAqd
ziURtyqcdFrEcDmSID4vtOF+Nm2Zgf5EJRoRCTUzWvEgcffCvWgfATcznKjnZExjGcbMYQKLhkWBKydzzi4/UE="
</form>
Cookie
__RequestVerificationToken_Lw__=+9UmEtsg2JLwoNO8eumR6fXWw/LnWU61oOiGvT+wNWPCrzfGSP++ODSJbUaeq7/7s
DBjW8vQq3QRpEIRERWGcTFVQUUNVfFx5FM4P8oy0V6vPUNayyn8QM3vCdgYx/WwumyKauQWJb+Ysduni9T2rbKjRXJ6LqLbIcTX8CuOU2g=
What is the use of ValidateAntiForgeryToken(Attribute) filter?
The ValidateAntiForgeryToken filter checks whether the request contains the security token or not and if it doesn't contains the token then it generates the following error.
A required anti-forgery token was not supplied or was invalid.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(Model model)
{
...
}
One disadvantage is this can't apply this filter at the global level because it check all the requests(GET, POST) for the security token and our requirement
is to check only for POST requests. The ValidateAntiForgeryTokenAttribute is a sealed class so we can't inherit and extend it instead we
have to create a brand new filter that composes it.
Custom AntiForgeryToken filter
public class AntiForgeryAttribute: IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext authorizationContext)
{
if (authorizationContext.RequestContext.HttpContext.Request.HttpMethod != "POST")
return;
new ValidateAntiForgeryTokenAttribute().OnAuthorization(authorizationContext);
}
}
The implementation is quite simple! our custom filter checks for the POST requests and if yes then instantiates and calls the OnAuthorization method
of the built-in ValidateAntiForgeryToken filter.
Now all we have to do is add our custom AntiForgeryAttribute as global filter.
filters.Add(new AntiForgeryAttribute());
Creating a secure html form
When we need all our HTML forms that needs to be secured this is what we do,
@using(Html.BeginForm())
{
...
Html.AntiForgeryToken()
}
It would be nice if we have a HTML helper that creates a form with security token hidden field and set the cookie.
Here is a simple helper!
public static MvcForm BeginSecureForm(this HtmlHelper htmlHelper,
string actionName, string controllerName)
{
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttribute("action",
UrlHelper.GenerateUrl(null, actionName, controllerName, new RouteValueDictionary(),
htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, true));
tagBuilder.MergeAttribute("method", "POST", true);
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
htmlHelper.ViewContext.Writer.Write(htmlHelper.AntiForgeryToken().ToHtmlString());
var theForm = new MvcForm(htmlHelper.ViewContext);
return theForm;
}
Now we can create a secure form simply like this:
@using(Html.SecureForm())
{
...
}
By passing AntiForgeryValidation for some actions
For some POST actions we may don't need the anti-forgery check and with the current implementation we can't achieve that. To satisfy this,
all we have to do is create a simple attribute and decorate the actions that doesn't need security check with that.
public class NoAntiForgeryCheckAttribute: Attribute
{
}
[NoAntiForgeryCheck]
public ActionResult NotSecuredAction(Model model)
{
}
We have to update our AntiForgeryAttribute to take care of that NoAntiForgeryCheckAttribute as below:
public class AntiForgeryAttribute: IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext authorizationContext)
{
if (authorizationContext.RequestContext.HttpContext.Request.HttpMethod != "POST")
return;
if (authorizationContext.ActionDescriptor.GetCustomAttributes(
typeof(NoAntiForgeryCheckAttribute), true).Length > 0)
return;
new ValidateAntiForgeryTokenAttribute().OnAuthorization(authorizationContext);
}
}
Yeah! we are done. Now all our POST actions are safe against CSRF vulnerability, hope so
I'm a software developer from south tip of India. I spent most of the time in learning new technologies. I've a keen interest in client-side technologies especially JavaScript and admire it is the most beautiful language ever seen.
I like sharing my knowledge and written some non-popular articles. I believe in quality and standards but blames myself for lagging them.
I believe in small things and they makes me happy!