Skip to main content

TinyMCE: anti-forgery token failed on file upload in an ASP.NET Boilerplate application

TinyMCE is an excellent full featured and well documented web editor that we can use in our web applications. But, integration with ASP.NET MVC 5 (as part of the ASP.NET Boilerplate technology stack) may be tricky to accomplish. When you set up the file upload plugin in TinyMCE, you have to consider the ASP.NET MVC 5 anti-forgery system. Otherwise, HTTP requests may fail loading resources to server due to an invalid or empty anti-forgery token error. This is a security and technology concern that can run out your time if you don't know about it.

This article explains how this mechanism works and shows some solutions in an ASP.NET Boilerplate application.


Background

ASP.NET MVC 5 has a built-in AntiForgery system to protect the applications from CSRF/XSRF attacks. Using @Html.AntiForgeryToken() in a Razor views, we can include an anti-forgery token in the HTML forms, to be validated in MVC controllers decorated with the ValidateAntiForgeryToken attribute.

Nowadays, in JavaScript-based and single page applications (SPAs), this mechanism presents some weaknesses with the AJAX requests, because those usually send JSON data to the server and not HTML forms. In these cases, other techniques are used, sending the anti-forgery token in the request header or cookie but we need to manually check the token validity.

Side-note: ASP.NET Core applications provide it's own tools for this problem (check this).

ASP.NET Boilerplate (ABP) includes a complementary mechanism for ASP.NET MVC 5 through the AbpAntiForgeryMvcFilter filter. This filter checks the token in the request header (in addition to HTML form field), and thus, we can use the anti-forgery protection for AJAX requests.

The main problem

Using TinyMCE, when it performs a file upload, it does not include the anti-forgery token in the header of the AJAX request, getting the following error:
Failed to load resource: the server responded with a status of 400 (Empty or invalid anti forgery header token.)

Solution

The general solution proposed in this post, is to include the anti-forgery token in the header of the AJAX request done by TinyMCE in the file upload process. To accomplish this, we need to override it's default upload file process, to include a parameter named X-XSRF-TOKEN with the token as value.

My environment:
  • ASP.NET MVC 5
  • ASP.NET Boilerplate 2.1.2.0
  • TinyMCE v4
  • Angular 1.4.8
In an ABP application, we can use abp.security.antiForgery.getToken() to get the token from the cookies.

Let's see three solutions.

JQuery solution

In the TinyMCE's settings, we can override images_upload_handler function, using a JQuery file upload implementation. This will be suffice because ABP intercepts the JQuery AJAX requests and includes the anti-forgery token in the header.
var tinymceOptions = {
    //...
    paste_data_images: true,
    automatic_uploads: true,
    file_picker_types: "image",
    images_upload_handler: function (blobInfo, success, failure) {
        $(function () {
            var formData = new FormData();
            formData.append("file", blobInfo.blob(), blobInfo.filename());

            $.ajax({
                type: "POST",
                timeout: 5000,
                url: "/file/UploadFile",
                data: formData,
                processData: false,
                contentType: false,
                success: function (data) {
                    //...
                    success(data.location);
                },
                error: function (error) {
                    //...
                    failure("HTTP Error: " + error.status);
                }
            });
        });
    }
};

Intercept XMLHttpRequest

AJAX requests are usually done using a XMLHttpRequest object. For this reason, it's possible to intercept this object and add the anti-forgery token in the header.
(function (send) {
        XMLHttpRequest.prototype.send = function (data) {
        this.setRequestHeader(
            abp.security.antiForgery.tokenHeaderName,
            abp.security.antiForgery.getToken());
        return send.call(this, data);
    };
})(XMLHttpRequest.prototype.send);

Raw XMLHttpRequest solution

In the TinyMCE's settings, we can override images_upload_handler function, and write our raw implementation and manually including the anti-forgery token. Note: this implementation was taken from TinyMCE documentation.

var tinymceOptions = {
    //...
    paste_data_images: true,
    automatic_uploads: true,
    images_upload_url: "/file/UploadFile",
    file_picker_types: "image",
    images_upload_handler: function (blobInfo, success, failure) {
        var xhr, formData;
        xhr = new XMLHttpRequest();
        xhr.withCredentials = true;
        xhr.open("POST", "/file/UploadFile");

        // get anti-forgery token XSRF-TOKEN from cookies and add it to request header as X-XSRF-TOKEN
        xhr.setRequestHeader(abp.security.antiForgery.tokenHeaderName, abp.security.antiForgery.getToken());

        xhr.onload = function () {
            var json;

            if (xhr.status != 200) {
                failure("HTTP Error: " + xhr.status);
                return;
            }

            json = JSON.parse(xhr.responseText);

            if (!json || typeof json.location != "string") {
                failure("Invalid JSON: " + xhr.responseText);
                return;
            }

            success(json.location);
        };

        formData = new FormData();
        formData.append("file", blobInfo.blob(), blobInfo.filename());

        xhr.send(formData);
    }
};

Conclusions

Once the solution is done, we can verify the anti-forgery token in the header request, and also in the cookies.


And that's all. I hope you find it useful!

Further Readings

Comments

  1. I'm a C++ developer but since I know you from a long time I needed to say something here. BTW, there was a time when I was doing web... :)

    Have you tried "Same-Site Cookies"? I seem to remember that CSRF attacks are dead when Same-Site Cookies are in place. They are just something of the past.

    Great blog, congratulations!

    ReplyDelete
    Replies
    1. Hi Julio, thanks for comment. I appreciate it.

      Same-Site Cookies is a great solution to prevent CSRF attacks and it's being supported for browsers, nowdays just Chrome and Opera.

      In the mean while, we need to use the ASP.NET MVC 5 (or Core) anti-forgery defence.

      Thanks again!

      Delete

Post a Comment

Popular posts from this blog

How to use Font Awesome with IText

This post is about on how to use Font Awesome with IText library to generate awesome icons in our PDF documents. In this post, we are using  ITextSharp , a C# port for IText, but those are the same functionalities.