15

In my config/application.rb file, I have this code,

config.action_dispatch.default_headers = {
        'Access-Control-Allow-Origin' => '*',
        'Access-Control-Request-Method' => 'GET, PATCH, PUT, POST, OPTIONS, DELETE'
    }

But that does not allow me to send a post request to a route on my sever

Safari gives this error:

http://localhost:3000/studentsFailed to load resource: the server responded with a status of 404 (Not Found)
http://localhost:3000/studentsFailed to load resource: Origin http://localhost:4200 is not allowed by Access-Control-Allow-Origin.
localhost:1XMLHttpRequest cannot load http://localhost:3000/students. Origin http://localhost:4200 is not allowed by Access-Control-Allow-Origi

And in my Rails server console:

Started OPTIONS "/students" for ::1 at 2015-03-28 21:00:45 -0500

ActionController::RoutingError (No route matches [OPTIONS] "/students"):
Ben Aubin
  • 5,542
  • 2
  • 34
  • 54

5 Answers5

23

I spent some time working on this and I can tell you the most reliable solution is to use rack-cors. see: https://github.com/cyu/rack-cors

First add the gem:

gem 'rack-cors', '~> 0.3.1'

then in application.rb add

config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
  allow do
    origins '*'
    resource '*', :headers => :any, :methods => [:get, :post, :options]
  end
end

If your production app does not serve static assets (such as if you use a serve like nginx or apache), consider replacing ActionDispatch::Static in the above example with 0. See https://github.com/cyu/rack-cors#common-gotchas for more information about the argument.

Achyut
  • 703
  • 9
  • 19
errata
  • 23,596
  • 2
  • 22
  • 32
  • this should work. i use `ActionDispatch::Static` as the first argument instead of `0`. – Reydi Sutandang Apr 03 '15 at 11:36
  • This worked very good for me. @ReydiSutandang, Why did you suggest `ActionDispatch::Static` instead of `0`? – andreshg112 Mar 25 '16 at 18:58
  • Now that I've worked with a new project, I've noticed that this solution makes a lot of sense for more complicated needs. I've selected it as the best answer – Ben Aubin May 19 '16 at 02:08
  • @ReydiSutandang Came back to this for a new project and realized that `ActionDispatch::Static ` isn't always the best answer. It's generally better than `0`, but it doesn't work for many environments (such as if you host static assets with NGINX, in which case, you wouldn't have `ActionDispatch::Static` in your production app). If you serve static assets through rails in production (which is generally a bad idea if you can avoid it), you should use `ActionDispatch::Static` instead of 0 – Ben Aubin Apr 01 '20 at 15:23
7

I was able to figure this out with a bit of help from @Akiomi's answer:

In my routes.rb, I added the following code to the top of the file:

  match '(:anything)' => 'application#nothing', via: [:options]

Next, in my application controller, I added:

def nothing
    render text: '', content_type: 'text/plain'
end

Along with the headers in config/application.rb:

config.action_dispatch.default_headers = {
    'Access-Control-Allow-Origin' => '*',
    'Access-Control-Request-Method' => 'GET, PATCH, PUT, POST, OPTIONS, DELETE',
    'Access-Control-Allow-Headers:' => 'Origin, X-Requested-With, Content-Type, Accept'
}

Yes, notice the 'Access-Control-Allow-Headers:' => 'Origin, X-Requested-With, Content-Type, Accept' that was not included in my original question, this is one of the big problems.

nathanallen
  • 424
  • 3
  • 10
Ben Aubin
  • 5,542
  • 2
  • 34
  • 54
2

Add the following code:

In config/routes.rb:

match 'students' => 'students#option', via: [:options]

In controllers/student_controller.rb:

def option
  render text: '', content_type: 'text/plain'
end

Or you can use rack-cors.

Ben Aubin
  • 5,542
  • 2
  • 34
  • 54
1

In some cases a browser will do a preflight request: rather than actually doing the request it first does an OPTIONS request to the same url, so that it can find out what the values of the various CORS headers are (More on preflighting here). If this request is successful and the headers have the correct values, it then does the actual request.

You haven't added a route for these options requests, so they're going through to the rails 404 page which doesn't include the CORS headers.

The OPTIONS response just needs to set the same CORS headers as you would normally set during a request. It shouldn't do anything else. For example

match 'students' => 'students#cors_preflight', via: [:options]

def cors_preflight
  render nothing: true
end

Note that there are other CORS headers you may need to set such as Access-Control-Allow-Credentials, Access-Control-Allow-Headers

When you've got this working you may wish to consider tightening this up a little - you are potentially opening your app to cross site scripting attacks.

Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174
  • It's a public api. I knew about the 404, but that doesn't work as you haven't included headers yet. Below I have answer my question in a way that works. – Ben Aubin Mar 31 '15 at 16:14
1

Rails 5

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*', headers: :any, methods: [:get, :post, :options]
  end
end
user398520
  • 1,553
  • 3
  • 12
  • 11