Giron
Giron

Reputation: 370

Rails - form doesn't submit any data during AJAX call - file upload - 422 status code

I have encountered a problem when trying to upload files with bootsy gem, but the problem is not related to bootsy itself. Bootsy generates a form with a following definition:

<form class="bootsy-upload-form form-inline" id="new_image" data-type="json" enctype="multipart/form-data" action="/bootsy/image_galleries/67/images" accept-charset="UTF-8" data-remote="true" method="post">
<input name="utf8" value="✓" type="hidden">
<input name="authenticity_token" id="authenticity_token" value="1pQMR5j33OKupMg4TSnQafLQx1BxzWjkP1KqTfZafqDRfGqwB8r2J4FzRE+dyuoHVOw/W0qd1FZ1JoJsJFThDQ==" type="hidden">
...

When I try to upload a file, this line is executed:

this.uploadInput.closest('form').submit();

I have added alert before it so I would see how the serialized data of the form looks like and all the fields are shown as expected (including authenticity token etc.):

alert(this.uploadInput.closest('form').serialize());

When the form is submitted, no data is send during POST request, only headers, nothing is seen in browser inspector, nothing can be seen in log files of rails, it just looks like this:

Started POST "/bootsy/image_galleries/47/images" for ::1 at 2016-02-05 11:20:04 +0100
Processing by Bootsy::ImagesController#create as HTML
Parameters: {"image_gallery_id"=>"47"}
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 0ms (ActiveRecord: 0.0ms)
FATAL -- : 
ActionController::InvalidAuthenticityToken - ActionController::InvalidAuthenticityToken:
_ actionpack (4.2.4) lib/action_controller/metal/request_forgery_protection.rb:181:in `handle_unverified_request'_

I have authenticity token generated in the form, I have the token in meta tag, everything looks fine, no error is thrown anywhere. I also tried to create sample app which is similar to my real project and it worked as expected, form normally submitted data - when I tried to compare HTML code and javascript events attached to both, they were almost similar expect a few parts because of other gems like ajax_pagination etc., but there were no parts, which should cause such a behavior.

I am using Rails 4.2.4, turbolinks are disabled using the attribute data-no-turbolink on body element, project uses bootstrap and contains JS libraries like jQuery, underscore, parsley, momentjs.

I would appreciate any thoughts, what could went wrong, why the form should not submit any data, where could be a problem. Thanks in advance for any tip.

UPDATE: Just to clarify things, I have taken a picture of a state before sending using AJAX - this javascript is part of jquery_ujs = RoR adapter for jQuery. You can see, that data contains all the form fields before send: enter image description here

But data is NOT being send to the server: enter image description here

On the other hand in my second working project, data is being sent: enter image description here

UPDATE 2: Just a few more information, bootsy gem, which is responsible for creation of the form uses remotipart to attach files to the request. Still...I was debugging the javascript and was unable to identify the problem. Both projects have the same version of jquery and remotipart, also the same version of rails. Looks like this will stay a mystery.

UPDATE 3: So I have almost resolved the issue - uploading is now working, it looks like it was a problem with order of javascript libraries. I'll post the result as soon as I pinpoint the exact issue - I'll reverse the changes and try to fix it again.

Upvotes: 9

Views: 1299

Answers (6)

Giron
Giron

Reputation: 370

The Issue was with an order of javascript libraries included in application.js. Bootsy was defined after jquery-fileupload. It seems, that Bootsy must be defined before it, with this setup, both works well. So state before:

//= require jquery-fileupload
//= require bootsy
//= require bootsy/locales/cs.js

after (working):

//= require bootsy
//= require bootsy/locales/cs.js
//= require jquery-fileupload

Upvotes: 2

lacostenycoder
lacostenycoder

Reputation: 11196

Without seeing your actual code, we are left at guesses and shooting in the dark, however as obvious as this may seem, I assume you've followed all gem instructions? The install generator uses asset pipeline and sets up initializers etc. This seems rather obvious but did you make sure to do whitelist bootsy param on the model in the app that's not working? (as per https://github.com/volmer/bootsy )

private

def post_params
  params.require(:post).permit(:title, :content, :bootsy_image_gallery_id)
end

Upvotes: 0

Daniel Batalla
Daniel Batalla

Reputation: 1264

I have two theories, none of them has nothing to do with your form:

First theory:

With the update of your question, if you look closely, cookies doesn't contain _csrf_token. If _csrf_token is not passed then Rails will raise that error message.

I can't see if your form is generating their own hidden field with this value. It may be that one of your projects do so and the other does't. You should check it out.

If that was the problem, there are various solutions that you can find. One of it is to include [csfr_meta_tag] in HTML head of the view. Check this out for more details.

Second theory:

If you are performing a CORS request, your browser makes an additional OPTIONS request first just to check that you do have permission to access this resource.

Apparently, _session_id cannot be sent in CORS, unless you enable it. To do so, you need to handle the OPTIONS request. This is done by modifying Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers headers on controller before_filter and after_filter methods.

Here lives a gist that shows an example of this (Forget the skip_before_filter line!). If you want to get more details of appointed headers values, check this resource out.

These are my theories, I hope that help you!

UPDATE

If both of your projects are using Rails v4.2, maybe the one that works has set protect_from_forgery like this:

protect_from_forgery
# OR maybe
protect_from_forgery with: :null_session

And the one that doesn't work has set protect_from_forgery like this:

protect_from_forgery with: :exception

The difference between them is the application behavior when a request is found to be unverified. See more here.

Upvotes: 0

B Seven
B Seven

Reputation: 45943

It seems that the Authenticity Token is not being sent to the server as expected.

I would look at the Network requests tab in the browser and inspect the body of the post.

I think setting processData to false is correct. According to the JQuery docs,

By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string

Also, you can add this in the controller before the check for the token:

before_filter { puts params }

I would not skip the Authenticity Token before filter.

Also, in your image "But data is NOT being send to the server:", you are looking at the headers. The Authenticity Token is sent in the body.

I think the issue is the way the form is being encoded. You should not encode it. Set the Content Type to multipart/form-data. The Content Type is currently set to 'application/x-www-form-urlencoded'.

The server is URL decoding the form, which is combining all the fields into one. That's why it doesn't find the Authenticity Token.

According to Sending multipart/formdata with jQuery.ajax, you should set contentType to false in the ajax request. Alternately, you can set it to multipart/form-data.

Upvotes: 1

ajayg
ajayg

Reputation: 44

By default the form only sends normal data.If you need to send image along with the post request you can use remotipart gem. It works great .I have used it myself.

Upvotes: 0

Sravan
Sravan

Reputation: 18647

the CRSF authentication issues are the cross site scripting issues.To overcome this issue, do either,

skip_before_action :verify_authenticity_token in your controller

or you can comment this line in application controller.

protect_from_forgery with: :exception  

Visit this link for more description on this

Upvotes: -1

Related Questions