Introduction
While trying to secure our ASP.NET MVC Web applications with recommended stuff like [ValidateAntiForgeryToken]
filter to be protected from CSRF attack.
Reference: OWASP – Cross-Site Request Forgery (CSRF)
I found that when we apply the traditional way, it’s more likely to forget these stuff somewhere, if it’s not you, it will be your teammates.
Traditional Way
[ValidateAntiForgeryToken]
public ActionResult AddUser(string userName)
{
return View();
}
// View
@using(Html.BeginForm("AddUser", "Home"))
{
@Html.AntiForgeryToken()
@Html.TextBox("userName")
<button type="submit">Save</button>
}
So why we don’t we do this stuff automatically for all Post
requests as recommended.
Automated Way
By applying these 6 easy steps, we will be able to protect our web applications from Cross-Site Request Forgery (CSRF).
Back End Work
- Security Filter Provider: This filter provider will apply
ValidateAntiForgeryToken
filter attribute on all Post
requests:
public class SecurityFilterProvider : IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext,
ActionDescriptor actionDescriptor)
{
List<Filter> filterSet = new List<Filter>();
string verb = controllerContext.HttpContext.Request.HttpMethod;
if (String.Equals(verb, "POST", StringComparison.OrdinalIgnoreCase)
&& !actionDescriptor.IsDefined(typeof(UnValidateAntiForgeryToken), true))
{
filterSet.Add(new Filter(new ValidateAntiForgeryTokenAttribute(),
FilterScope.Global, null));
}
return filterSet;
}
}
- Register Filter Provider in Global.asax:
protected void Application_Start()
{
...
FilterProviders.Providers.Add(new SecurityFilterProvider());
...
}
- This filter skips
Action
from being validated automatically:
public class UnValidateAntiForgeryToken : ActionFilterAttribute
{
}
[HttpPost]
[UnValidateAntiForgeryToken]
public ActionResult AddUser(string userName)
{
...
}
Front End Work
- Render
AntiForgeryToken
input variable, this should be in Layout
or View
.
var antoForgeryToken = '@Html.AntiForgeryToken()';
- This will post
AntiForgeryToken
with forms:
$('form').submit(function (event) {
if ($(this).attr("method").toUpperCase() == "POST"
&& !$(this).find("[name=" + $(antiForgeryToken).attr("name") + "]").length) {
$(this).append($(antiForgeryToken));
}
});
- This will post
AntiForgeryToken
with Jquery-Ajax, and Ajax Action Links:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (options.type.toUpperCase() == "POST") {
if (!originalOptions.data.__RequestVerificationToken) {
var token = { __RequestVerificationToken: $(antiForgeryToken).val() };
var data = $.isArray(originalOptions.data) ?
originalOptions.data[0] : originalOptions.data;
$.extend(data, token);
options.data = $.param(data);
}
}
});
Recommended
We can use @Html.BeginSecureForm
instead of BeginForm
, and @Ajax.BeginSecureForm
.
This Overload
methods adds AntiForgery
Token to the form:
// View
@using (Html.BeginSecureForm("AddUser", "Home"))
{
@Html.TextBox("userName")
<button type="submit">Save</button>
}
public static MvcForm BeginSecureForm
(this HtmlHelper htmlHelper, string actionName, string controllerName)
{
var form = htmlHelper.BeginForm(actionName, controllerName);
htmlHelper.ViewContext.Writer.Write(htmlHelper.AntiForgeryToken().ToHtmlString());
return form;
}
public static MvcForm BeginSecureForm(this AjaxHelper ajaxHelper, AjaxOptions ajaxOptions)
{
var form = ajaxHelper.BeginForm(ajaxOptions);
ajaxHelper.ViewContext.Writer.Write(AntiForgery.GetHtml());
return form;
}
All overloading methods of BeginSecureForm
is found at SecureFormExtensions.cs and SecureAjaxExtensions.cs.