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.
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).
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
My environment:
And that's all. I hope you find it useful!
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 namedX-XSRF-TOKEN
with the token as value.- ASP.NET MVC 5
- ASP.NET Boilerplate 2.1.2.0
- TinyMCE v4
- Angular 1.4.8
In an ABP application, we can use
Let's see three solutions.
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!
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... :)
ReplyDeleteHave 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!
Hi Julio, thanks for comment. I appreciate it.
DeleteSame-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!