rails: Missing Authenticity Token in Remote Forms w/ File Upload Field(s)

This is more-or-less a cross-post of https://github.com/rails/jquery-ujs/issues/451.

This issue deals with form_for tags containing one or more file_fields where :remote => true.

config.action_view.embed_authenticity_token_in_remote_forms defaults to false to facilitate fragment caching (which makes total sense), but the jquery-ujs gem is currently coded to permit (without alteration – meaning it will submit as non-ajax) the submit event of any form if:

  • the data-remote attribute in <form> tag is set to true; and,
  • the <form> tag contains one or more <input type="file" /> children; and,
  • any <input type="file" /> tag has a file selected for upload

(Note that, by design, jquery-ujs does not provide a method to submit file uploads via AJAX. It only provides a non-AJAX fallback. To submit a remote form with file uploads via AJAX, one must include javascript that catches the submit action before jquery-ujs does.)

Long story short, there is an issue in that jquery-ujs cannot currently fulfill its role to provide a non-AJAX fallback since there is no hidden input element in the <form> provided by actionview (and jquery-ujs is not using the page’s meta tags as it does for remote requests) containing an authenticity token, so none is POSTed an InvalidAuthenticityToken is raised in response to the form submission.

The question du jour is where this bug should be fixed: should a hidden input element always be inserted by actionview in remote form_fors in which a file_field is present (regardless of config.action_view.embed_authenticity_token_in_remote_forms)? (This is the solution suggested by the jquery-ujs owner.) Alternatively, should jquery-ujs create a hidden authenticity_token input tag inside the <form> dynamically (with the value from the page’s meta tags) immediately before it is about to fulfill its fallback role (my inclination).

Please advise/discuss/etc. (I’m happy to contribute a PR resolving the issue in whatever manner is mutually agreeable, but obviously either rails or jquery-ujs must be willing to merge it.)

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 3
  • Comments: 19 (10 by maintainers)

Most upvoted comments

Wow, I’ve been tracking this down for hours, and it was painful. It’s exactly as @mvastola describes it—CSRF fails when a form (1) has file inputs and (2) has remote: true.

My instinct is that this should be fixed in jquery-ujs. I documented my opinion on a fix here: https://github.com/rails/jquery-ujs/issues/451#issuecomment-258547646

An easy workaround is to add authenticity_token: true to your form_for:

<%= form_for @user, remote: true, authenticity_token: true do |f| %>
  <%= f.file_field :image %>
<% end %>

That workaround is easy if you’re not fragment caching forms, and ignores the documentation’s statement that you should only use the :authenticity_token to customize or hide the field:

Use only if you need to pass custom authenticity token string, or to not add authenticity_token field at all (by passing false).

Perhaps another way to “fix” this is to always include the authenticity token, and to instruct users who care about fragment caching to use authenticity_token: false on form_for. They’re already having to perform nuanced coding around where to place cache statements, it seems reasonable that they should also have to remove CSRF tokens before caching.

This issue has been automatically closed because of inactivity.

If you can still reproduce this error on the 5-0-stable branch or on master, please reply with all of the information you have about it in order to keep the issue open.

Thank you for all your contributions.

I can confirm, this still happens on rails 5.2.1

I can confirm that this bug still exists on Rails 5.0.6.

I’ve got the same issue with rails 5.2.1. The problem is that when setting remote: true in form_for, csrf_token won’t be sent with file upload. In order to resolve this you should:

  1. set authenticity_token: true
<%= form_for @user, remote: true, authenticity_token: true do |f| %>
  <%= f.file_field :image %>
<% end %>
  1. Enable the direct upload of the upload module that you are using, for exemple if you are using ActiveStorage so you should work with ActiveStorage npm module to activate the direct upload.

Go drunk, @rails-bot[bot], you’re home.

That’s certainly one way to fix the bug. Pretend it doesn’t exist.

One more thing, then I will let wiser people advise…

While debugging this behavior in my app, the most confusing thing going on was that setting config.action_controller.per_form_csrf_tokens = true didn’t insert an HTML element in my forms when using remote: true. For a while, I believed that config option was plain not working, deprecated, or who knows what.

It seems like the nuance that per-form CSRF tokens are never inserted on forms with remote: true should be documented here: http://edgeguides.rubyonrails.org/configuring.html Or like I advised, the default behavior should be always insert them unless the form_for also has authenticity_token: false.

I’m happy to writeup a PR with doc updates. Just trying to help. Thanks for your work on Rails, I ❤️ it.

I just wanted to drop a brief note here and mention that I just ran into this issue. I noticed the ActionController::InvalidAuthenticityToken error when submitting a remote: true form with JS turned off. I expected it to fallback to a regular form submission.

I like @aguynamedben’s suggestion to include the token by default in order to support fallback.