Search code examples
ruby-on-railsroutessubdomaincors

request.xhr CORS Layout Fail


If I send a remote: true request to our subdomain controller, our layout renders (which it shouldn't)

Having tested the request.xhr? method in the action, it's returning nil (not the true / false) that you'd expect. This works for non-CORS ajax. It only stops working with CORS-ajax (for subdomain)


Here's the code:

#app/views/controller/view.html.haml
= link_to "test", new_user_session_url, remote: :true

#routes
new_user_session_path   GET  /login(.:format)    admin/users/sessions#new {:subdomain=>"test"}

The response occurs & we get a layout

We want no layout, which works without CORS:

#app/controllers/application_controller.rb
layout :layout_select

  def layout_select
    if request.xhr?
      false
    else
      devise_controller? ? "admin" : "application"
    end
  end

We have CORS policy set up & working. However, it seems our CORS-request is not being treated as xhr. Any ideas?


Solution

  • In light of the comments from Mike Campbell, I was able to determine that CORS requests are not treated as ajax (xhr) by default

    The problem lies in the headers which are passed on an Ajax request. Standard ajax passes the following header:

      #actionpack/lib/action_dispatch/http/request.rb
      def xhr?
           @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/ 
      end
    

    This is passed with a "naked" Ajax request, which allows the xhr? method to return either true or false. The issue is in a CORS ajax call, this header is not passed. I don't know why, but it just sends an ORIGIN header instead

    The proposed fix from Github suggested to include this:

     uri.host != @env['HTTP_HOST'] || uri.scheme != @env['rack.url_scheme'] || uri.port != @env['HTTP_PORT'].to_i
    

    This would basically return a boolean response based on whether the request was from the same domain as the script. Although this was prevented for being unsafe, it lead me to two fixes:

    1. Append a new cors? method to the Rails core
    2. Send the http_x_requested_with header through a CORS request

    Considering we are just trying to access a subdomain with the request, we felt that editing the Rails core was slightly overkill, although right. Instead, we found that using the inbuilt headers argument in jquery would help:

    $.ajaxSetup({ headers: {"X-Requested-With": "XMLHttpRequest"}});
    

    Whilst this works, I am eager to hear about any security issues it may present. And plus, whether we can access json and other formats with it