Search code examples
jqueryasp.net-mvc-5xmlhttprequesthttp-post

Is it possible to include antiforgerytoken in XMLHttpRequest header?


I am using a library called Slim, to upload images to my asp.net MVC project.

The JQuery library is using XMLHttpRequest to send requests to the server, I have gone through all the manual and there is nothing about how to include anti forgery token to the server.

So I manually added this line before the send:

data.__RequestVerificationToken = $('input[name="__RequestVerificationToken"]').val();

Even though the token is included in the data, the Asp.net MVC filter: ValidateJsonAntiForgeryTokenAttribute is not recognizing it, as it is expecting the token to be in the header. here is the validation code on the server:

AntiForgery.Validate(cookie != null ? cookie.Value : null,
                             httpContext.Request.Headers["__RequestVerificationToken"]);

and here is the library's send function with the extra line that I have added at the buttom to include the token. How can include the token in the header?

var send = function send(url, data, requestDecorator, progress, success, err) {

var xhr = new XMLHttpRequest();

// if progress callback defined handle progress events
if (progress) {
    xhr.upload.addEventListener('progress', function (e) {
        progress(e.loaded, e.total);
    });
}

// open the request
xhr.open('POST', url, true);

// if request decorator defined pass XMLHttpRequest instance to decorator
if (requestDecorator) {
    requestDecorator(xhr);
}

// handle state changes
xhr.onreadystatechange = function () {

    if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {

        var text = xhr.responseText;

        // if no data returned from server assume success
        if (!text.length) {
            success();
            return;
        }

        // catch possible PHP content length problem
        if (text.indexOf('Content-Length') !== -1) {
            err('file-too-big');
            return;
        }

        // if data returned it should be in suggested JSON format
        var obj = void 0;
        try {
            obj = JSON.parse(xhr.responseText);
        } catch (e) {}

        // if is failure response
        if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj.status === 'failure') {
            err(obj.message);
            return;
        }

        success(obj || text);
    } else if (xhr.readyState === 4) {

        var _obj = void 0;
        try {
            _obj = JSON.parse(xhr.responseText);
        } catch (e) {}

        // if is clean failure response
        if ((typeof _obj === 'undefined' ? 'undefined' : _typeof(_obj)) === 'object' && _obj.status === 'failure') {
            err(_obj.message);
            return;
        }

        err('fail');
    }
};

// I have added this line to library to include anti forgery token
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
data.__RequestVerificationToken = $('input[name="__RequestVerificationToken"]').val();

// do request
xhr.send(data);

}; `


Solution

  • You need to set the antiforgerytoken in the header like this:

    xhr.setRequestHeader('__RequestVerificationToken', $('input[name="__RequestVerificationToken"]').val());
    

    see XMLHttpRequest.setRequestHeader for more information about XMLHttpRequest:

    To verify the AntiforgeryToken passed in the header, in your controller, you need to create your own filter, like this:

    [HttpPost]
    [ValidateJsonAntiForgeryToken]
    public ActionResult MySlimAction()
    {
        /* your Save controller logic goes here */
    }
    

    Add this filter definition as a new class to your Filters folder:

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    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["__RequestVerificationToken"]);
        }
    }
    

    In case of your specific library, Slim Image Cropper, you can use willRequest call back while initializing Slim:

    var cropper = $('#my-cropper-selector').slim({
      willRequest: function (xhr) {
        xhr.setRequestHeader('__RequestVerificationToken', $('input[name="__RequestVerificationToken"]').val());
      },
      ratio: 'free',
      push: true,
      service: '/MyController/MySlimAction'
      /* other options go here */
    });