Anti forgery exceptions when making ajax posts using JSON

Topics: Core
May 22, 2013 at 6:04 PM
I recently ran into this problem and spent a good chunk of a day running through different threads and struggled to find a solution that I liked. Most of them wanted me to move away from JSON posts, but I feel like I should be able to do a JSON post if I want to. I'm not certain this is the best/correct solution, but it seems to work.

Eventually I ran into:
http://haacked.com/archive/2011/10/10/preventing-csrf-with-ajax.aspx

But when I attempted to duplicate it I found that the MVC code had changed since this was created which lead me to pulling down the MVC 4 source (https://aspnetwebstack.codeplex.com/).

This all led me to editing the existing ValidateAntiForgeryTokenOrchardAttribute located in the orchard.framework project:
    [AttributeUsage(AttributeTargets.Method)]
    public class ValidateAntiForgeryTokenOrchardAttribute :
    FilterAttribute, IAuthorizationFilter
    {
        private string _salt;

        public ValidateAntiForgeryTokenOrchardAttribute()
            : this(System.Web.Helpers.AntiForgery.Validate)
        {
        }

        internal ValidateAntiForgeryTokenOrchardAttribute(Action validateAction)
        {
            Debug.Assert(validateAction != null);
            ValidateAction = validateAction;
        }

        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AdditionalDataProvider", Justification = "API name.")]
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AntiForgeryConfig", Justification = "API name.")]
        [Obsolete("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.", error: true)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public string Salt
        {
            get { return _salt; }
            set
            {
                if (!String.IsNullOrEmpty(value))
                {
                    throw new NotSupportedException("The 'Salt' property is deprecated. To specify custom data to be embedded within the token, use the static AntiForgeryConfig.AdditionalDataProvider property.");
                }
                _salt = value;
            }
        }

        internal Action ValidateAction { get; private set; }

        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            var request = filterContext.HttpContext.Request;
            if (!string.IsNullOrEmpty(request.Headers["__RequestVerificationToken"]))
            {
                //We use the token in the header to support json requests.
                var cookieToken = request.Cookies["__RequestVerificationToken"] == null
                    ? "" : request.Cookies["__RequestVerificationToken"].Value;
                var formToken = request.Headers["__RequestVerificationToken"];
                System.Web.Helpers.AntiForgery.Validate(cookieToken, formToken);
            }
            else
            {
                ValidateAction();
            }
        }
    }
}
Which is basically the asp.net 4 ValidateAntiForgeryTokenAttribute with this chunk added to it:
    if (!string.IsNullOrEmpty(request.Headers["__RequestVerificationToken"]))
    {
        //We use the token in the header to support json requests.
        var cookieToken = request.Cookies["__RequestVerificationToken"] == null
            ? "" : request.Cookies["__RequestVerificationToken"].Value;
        var formToken = request.Headers["__RequestVerificationToken"];
        System.Web.Helpers.AntiForgery.Validate(cookieToken, formToken);
    }
This basically changes the AntiForgery validation to also check the headers of the request for the request verification token. Next I changed the AntiForgeryAuthorizationFilter (also located in the orchard.framework folder) to use the updated ValidateAntiForgeryTokenOrchardAttribute instead of the old ValidateAntiForgeryTokenAttribute:
var validator = new ValidateAntiForgeryTokenOrchardAttribute();
At this point you should then be able to make Ajax posts using JSON something like this:
               var headers = {};
        headers['__RequestVerificationToken'] = '@(Html.AntiForgeryTokenValueOrchard())';
        $.ajax
        ({
            type: "Post",
            url: url,
            async: true,
            dataType: "json",
            headers: headers,
                contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(postData), 
            success: function (response) {
            }
        });
If anyone happens to know this is terribly wrong for some reason please let me know! Hopefully it helps save someone else a little time.

PS: Inside the AntiForgeryAuthorizationFilter there's some similar sort of action put into play by Orchard going on around:
//HAACK: (erikpo) If the token is in the querystring, put it in the form so MVC can validate it
I was unable to fix my issue by duplicating that sort of logic so I'm not actually sure that this still works, it might make more sense to handle it similar to how this was done.
Coordinator
May 22, 2013 at 6:18 PM
May 22, 2013 at 7:04 PM
That will work fine because the form will include the RequestVerificationToken, however that is not posting as JSON (Which doesn't work because when you post as content type = JSON it no longer stores the RequestVerificationToken in the form, but in the JSON data which is not checked by the Anti Forgery Validation).