65.9K
CodeProject is changing. Read more.
Home

Validate Anti-Forgery in ASP.NET MVC- The Automated Way

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (7 votes)

Feb 12, 2017

CPOL
viewsIcon

26150

downloadIcon

377

Validate Anti-Forgery in 6 easy steps

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

// Action

[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

  1. 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>();
            // ValidateAntiForgeryToken for POST requests + 
            // Skip Actions with UnValidateAntiForgeryToken
            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;
        }
    }
  2. Register Filter Provider in Global.asax:
    protected void Application_Start()
    {
       ...
       FilterProviders.Providers.Add(new SecurityFilterProvider());
       ...
    }
  3. This filter skips Action from being validated automatically:
    public class UnValidateAntiForgeryToken : ActionFilterAttribute
    {
        // This Filter has no use except skipping AntiForgeryToken Attribute from taking place
    }
    
    [HttpPost]
    [UnValidateAntiForgeryToken]
    public ActionResult AddUser(string userName)
    {
        ...
    }

Front End Work

  1. Render AntiForgeryToken input variable, this should be in Layout or View.
    var antoForgeryToken = '@Html.AntiForgeryToken()';
  2. This will post AntiForgeryToken with forms:
    // Append AntiForgeryTiken Input to Form
    $('form').submit(function (event) {
        if ($(this).attr("method").toUpperCase() == "POST" 
        && !$(this).find("[name=" + $(antiForgeryToken).attr("name") + "]").length) {
            $(this).append($(antiForgeryToken));
        }
    });
  3. This will post AntiForgeryToken with Jquery-Ajax, and Ajax Action Links:
    // Append AntiForgeryToken to Jquery Ajax Requests$.param()
    $.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>
}

 

// BeginForm replacement for HtmlHelper
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;
}

// BeginForm replacement for AjaxHelper
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.