4

I know this has been brought up a few times on SO but I cannot make inroads. I'm on Rails 5.0.7.2.

I am getting ActionController::InvalidCrossOriginRequest thrown hundreds of times per day. Rollbar says it is mostly caused by Bingbot, and it's on my user signup modal, which is from Devise. It's a GET request, and the path is /users/sign_up.

NOTE: It's not a form. It's a modal that simply asks the user How would you like to sign up? Email, Fb, Google auth, etc.

Basically I have some javascript actions on my site, picture like a reddit or stackoverflow upvote. I am not using remote: true on those links, but rather I have an $.ajax call that handles the upvote like so:

var token = document.querySelector('meta[name="csrf-token"]').content;

$.ajax({
  // For the upvote, type will be POST
  type: $this.data('method'),
  beforeSend: function(xhr) {
    xhr.setRequestHeader('X-CSRF-Token', token)
  },
  url: $this.data('url'),
  success: function(data) {
    // Some cleanup...
  }
}); 

Once a logged out visitor tries to upvote, it fires the ajax request which is then halted (not authorized) and redirected, like this:

Started POST "/upvotes/toggle_vote"
Processing by UpvotesController#toggle_vote as */*
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)


Started GET "/users/sign_up"
Processing by RegistrationsController#new as */*

Here's the Devise controller which I've overwritten:

class RegistrationsController < Devise::RegistrationsController

  def new
    # ... other code        

    respond_to do |format|
      format.js   { render layout: false }
      format.html { respond_with self.resource }
    end
  end
end

Inside of /views/devise/registrations I have new.js.erb which opens up the "how would you like to sign up?" modal.

WHAT I'VE TRIED:

  • I don't think solving it by adding protect_from_forgery except: :new is the right answer (as has been proposed in similar questions), since that's inherently not safe.

  • I tried reordering the respond_to formats per this but that makes the signup modal not open.

  • I tried to throw in a format.any { raise ActionController::RoutingError.new("Not Found") } catch-all in the respond_to block but that did nothing to fix the error.

  • As you can see in the $.ajax call above I even try to set the X-CSRF-Token header.

The full error text by the way is this:

ActionController::InvalidCrossOriginRequest: Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript

Any ideas?

Could it be that the authenticity token is getting lost since UpvotesController#toggle_vote redirects to RegistrationsController#new and changes from a POST to a GET?

DelPiero
  • 489
  • 1
  • 10
  • 21
  • Does this answer your question? [Rails InvalidCrossOriginRequest](https://stackoverflow.com/questions/29310187/rails-invalidcrossoriginrequest) – Juan Artau Apr 17 '21 at 00:08
  • It does not unfortunately, because I think `protect_from_forgery` should be there for security. There was no other proposed solution in that thread. Also, I am not mixing http/https resources. – DelPiero Apr 19 '21 at 14:20
  • how about: `protect_from_forgery unless: -> { request.format.js? }`, only for your some javascript actions. – Lam Phan Apr 20 '21 at 08:21
  • Can you share your code for the javascript action (front end)? And also - are you using jquery-rails or rails-ujs? – Joel Blum Apr 21 '21 at 17:21
  • @Joel_Blum I'm using jquery-rails. I cleaned up the question a bit, you can now see my `$.ajax` call. – DelPiero May 17 '21 at 16:45
  • @DelPiero I have literally the exact same scenario as you describe. I don't see any solutions here other than the lazy "protect_from_forgery". Did you ever get this resolved? – Matt Weick Nov 01 '22 at 20:55

5 Answers5

3

Couple of thoughts:

  1. You shouldn't put the csrf token yourself, both jquery-rails or rails-ujs (I'm not sure which one you're using) do it for you; with rails-ujs you need to use the Rails.ajax method

  2. There is no redirecting POST to GET and keeping the original POST headers , that means you indeed lose the csrf token in GET request (however you usually don't send or need a CSRF token in a GET request anyway so check why you are doing that maybe).

Joel Blum
  • 7,750
  • 10
  • 41
  • 60
  • Regarding #1, I am using jquery-rails. Regarding #2, you are right, that all makes sense. If you look at the log output in my question, it's from the way Devise behaves. It tries to run the `/upvotes/toggle_vote` action, which is protected by `before_action :authenticate_user!`. So then control is passed to `RegistrationsController#new`. – DelPiero May 17 '21 at 17:57
2

You need to send an authenticity token with ajax requests that modify resources in the server (post/put/patch/delete). Usually, rails add it for you as a hidden field in the form.

  • Ah, sorry I edited the question to clarify. The modal is actually not a form. – DelPiero May 08 '21 at 14:04
  • It doesn't matter if it's a form or not, you still need to provide a token for rails to accept the request. –  May 08 '21 at 14:23
  • I have `<%= csrf_meta_tags %>` in the head of my application layout. Does that not cover that? – DelPiero May 08 '21 at 14:39
  • if you're doing an ajax (js) request, try grabbing that token and attach it to the request: ```const token = document.querySelector('meta[name="csrf-token"]').content``` –  May 08 '21 at 14:56
  • Check out my updated question. Inside of the `$.ajax` call I try to set the authenticity token. However now I'm thinking that since there's a redirect and a switch from POST to GET it might not be sending it through? Is that possible? – DelPiero May 17 '21 at 16:48
  • can you show the upvotes controller? @DelPiero –  May 18 '21 at 08:08
2

Adding this inside your form should do the trick:

<% hidden_field_tag :authenticity_token, form_authenticity_token %>
Dan Tappin
  • 2,692
  • 3
  • 37
  • 77
  • Sorry I should have specified, the modal is actually *not* a form. I know Devise routes usually are, but in this case the modal that pops up simply asks the user how they'd like to proceed. Sign up with email/facebook/etc. – DelPiero May 08 '21 at 14:02
1

It looks like a CORS issue to me, just like the error suggest there I an API call made fro an embedded javascript which is on a different domain than the one defined on your server. This triggers a CORS error. Usually to fix CORS error you need to add a header Access-Control-Allow-Origin with the the domain of the website in the list of allowed domain. You can easily do this with the rack cors gem: https://github.com/cyu/rack-cors

Typpex
  • 488
  • 4
  • 11
0

In Rails 5 I found that

config.action_view.embed_authenticity_token_in_remote_forms = true

avoid the CORS issue on same domain remote calls, but it seems that in Rail 6 is not effective.

alectrico
  • 15
  • 6