Skip to content

24b Cross Origin Requests

Dave Strus edited this page Jul 18, 2015 · 1 revision

Cross-Origin Requests

If we want to allow 3rd party clients to use the JSON provided via JavaScript and Ajax, we need to tell the web browsers that request through the API, that it is okay to use.

Without this client developers will get errors like XMLHttpRequest cannot load http://yoursite.com/notes.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

We need to enable [CORS - Cross Origin Request Sharing], by setting a few headers.

This is a pretty good article explaining why.

Since we only need this for the API, and we want it to cover the entire API, let's add a before_action to API::APIController.

app/controllers/api/api_controller.rb

class API::APIController < ApplicationController
  # ...
  before_action :set_access_control_headers
  # ...
  private

  def set_access_control_headers
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
    headers['Access-Control-Request-Method'] = '*'
    headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
  end
  # ...

Now this will set the correct headers, however, some JavaScript clients will perform a "preflight" of the request with the OPTIONS HTTP method (which is usually not used in a normal Rails project).

Rails' RESTful route declarations (like resources :users), won't create route entries for OPTIONS method requests, so we need to either add them to each resource that can be accessed or, since this is an API, write a blanket route to cover everything under /api/. Add this line to the routes.rb file:

config/routes.rb

  match '/api/*path' => 'api/api#preflight', via: [:options]

And add a public method to api_controller.rb to serve a 200 response, no matter what. Our app doesn't really need or support OPTIONS, but we need to conform to the spec. Since we don't actually need the body of the response, just a message saying "we got your message ok".

app/controllers/api/api_controller.rb

  def preflight
    head :ok
  end

Now, you should be able to access the API over Ajax with something like:

$.get('http://yoursite.com/api/v1/notes.json?api_key=' + my_key +, function(data) {
  console.log(data);
});

This kind of seems a bit hackish, but now I think we understand the issue a bit more clearly.

In true Rails fashion, let's replace it with a gem.

In API::APIController, remove the before_action for :set_access_control_headers, as well as the method itself, and also remove the preflight action/method.

In routes.rb, remove the match '/api/*path' catchall route for OPTIONS requests.

In your Gemfile, add the Rack::Cors rack middleware and bundle:

Gemfile

gem 'rack-cors', require: 'rack/cors'
$ bundle install

Then, add a configuration that covers everything under /api/ to config/application.rb, inside the class Application declaration:

config/application.rb

    # Rack::Cors configuration
    config.middleware.insert_before 0, 'Rack::Cors', debug: true, logger: Rails.logger do
      allow do
        origins '*'
        resource '/api/*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options], max_age: 0
      end
    end

This will intercept those requests and set the correct headers for us.

Commit.