[Security] Preventing CSRF attacks (Implementing AntiForgery)

awesomegithubusername edited this page Jul 6, 2016 · 4 revisions
Clone this wiki locally

Hi everyone;

Credits: I borrowed heavily from this post.

In my first contribution to the community of this great platform, I will (try to) show how to prevent CSRF attacks. To understand what we're up against, take a look at this informative video from Troy Hunt and this post from Sumit Maitra.

Basically, you'd wanna add the "AntiForgeryToken()" to your submit forms. That would be a very straightforward task if we were sending HTML form data. Because our AJAX request send JSON data, we may follow these steps and we'll be all set:

First, add "@Html.AntiForgeryToken()" to your submit button (AccountLogin.cshtml). This will generate the AntiForgeryToken and the Cookie on the client side:

                <button id="~_LoginButton" type="submit" class="btn btn-primary">
                    @Html.AntiForgeryToken()  //<--------------------------------------
                    @Texts.Forms.Membership.Login.SignInButton
                </button>

Modify LoginPanel.ts. This will send the previously generated token to the Login method :

           this.byId('LoginButton').click(e => {
                e.preventDefault();

                if (!this.validateForm()) {
                    return;
                }

                var request = this.getSaveEntity();

                var token = $('input[name="__RequestVerificationToken"]').val();//<----------
                var headers = {};//<--------------------------------------------------------------------------
                headers['X-RequestVerificationToken'] = token;//<----------------------------------

                Q.serviceCall({
                    url: Q.resolveUrl('~/Account/Login'),
                    request: request,
                    headers: headers,  // <--------------------------------
                    onSuccess: function (response) {
                        var q = Q.parseQueryString();
                        var returnUrl = q['returnUrl'] || q['ReturnUrl'];
                        if (returnUrl) {
                            window.location.href = returnUrl;
                        }
                        else {
                            window.location.href = Q.resolveUrl('~/');
                        }
                    }
                });

Define "ValidateJsonAntiForgeryTokenAttribute":

namespace MultiTenancy

{  using System;
    using System.Web;
    using System.Web.Helpers;
    using System.Web.Mvc;

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
                AllowMultiple = false, Inherited = true)]
    public sealed class ValidateJsonAntiForgeryTokenAttribute
                            : FilterAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            var httpContext = filterContext.HttpContext;
            var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
            AntiForgery.Validate(cookie != null ? cookie.Value : null,
                                 httpContext.Request.Headers["X-RequestVerificationToken"]);
        }
    }
}

Finally, add the "ValidateJsonAntiForgeryToken" attribute to the POST login method of the Account controller:

        [HttpPost, JsonFilter]
        [ValidateJsonAntiForgeryToken] <------------
        public Result<ServiceResponse> Login(LoginRequest request)
        {
            return this.ExecuteMethod(() =>
            {
                //code
            });
          }

These same simple steps should be applied to the other submit forms (such as the SignUp and ChangePassword forms).