Reputation: 8158
I am having a horrible time trying to get CORS working properly in my JSON api Rails app.
I have an Event
model and associated controller. Event
s belong to RegisteredApplication
s.
# Routes.rb
Rails.application.routes.draw do
root 'registered_applications#index'
resources :registered_applications
namespace :api, defaults: { format: :json } do
# OPTION is an HTTP verb
match '/events', to: 'events#create', via: [:options]
resources :events, only: [:create]
end
devise_for :users
end
# events_controller.rb
class API::EventsController < ApplicationController
skip_before_filter :verify_authenticity_token
before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers
# If this is a preflight OPTIONS request, then short-circuit the request,
# return only the necessary headers and return an empty text/plain.
def cors_preflight_check
if request.method == :options
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version'
headers['Access-Control-Max-Age'] = '1728000'
render :text => '', :content_type => 'text/plain'
end
end
# For all responses in this controller, return the CORS access control headers.
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
headers['Access-Control-Max-Age'] = "1728000"
end
def create
registered_application = RegisteredApplication.find_by(url: request.env['HTTP_ORIGIN'])
if registered_application.nil?
render json: "Unregistered application", status: :unprocessable_entity
else
event = registered_application.events.new(event_params)
if event.save
render json: @event, status: :created
else
render @event.errors, status: :unprocessable_entity
end
end
end
private
def event_params
params.permit(:name)
end
end
The javascript I'm using to send new click events to my Rails app:
$( document ).ready(function() {
$( window ).click( function() {
metrics.report("click");
});
});
var metrics = {};
metrics.report = function(eventName) {
var event = { name: eventName };
var request = new XMLHttpRequest();
request.open("POST", "http://localhost:3000/api/events", true);
request.setRequestHeader('Content-Type', 'application/json');
request.send(JSON.stringify(event));
}
There are two issues:
1) When I make the request via the above javascript (from within another web page) I get a XMLHttpRequest cannot load http://localhost:3000/api/events. Request header field Content-Type is not allowed by Access-Control-Allow-Headers
error. My Rails api requires the request to be in JSON so I don't know what I am supposed to do about that. If I comment out the setRequestHeader()
line then the error goes away but then my Rails app doesn't receive the initial HTTP OPTIONS
request.
2) On the Rails app side I see an OPTIONS request but the second (POST) request never occurs. This is the only thing in my log:
Started OPTIONS "/api/events" for ::1 at 2015-08-30 12:54:17 -0400
Processing by API::EventsController#create as JSON
RegisteredApplication Load (0.2ms) SELECT "registered_applications".* FROM "registered_applications" WHERE "registered_applications"."url" = $1 ORDER BY created_at DESC LIMIT 1 [["url", "http://localhost:2222"]]
Unpermitted parameter: format
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO "events" ("registered_application_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["registered_application_id", 21], ["created_at", "2015-08-30 16:54:17.751257"], ["updated_at", "2015-08-30 16:54:17.751257"]] (0.4ms) COMMIT
Completed 201 Created in 11ms (Views: 0.1ms | ActiveRecord: 1.1ms)
Am I missing something obvious?
Upvotes: 2
Views: 1630
Reputation: 88408
I think you need to add headers['Access-Control-Allow-Headers'] = "Content-Type"
in your cors_set_access_control_headers
function in your server-side code.
My Rails api requires the request to be in JSON so I don't know what I am supposed to do about that. If I comment out the
setRequestHeader()
line then the error goes away but then my Rails app doesn't receive the initial HTTP OPTIONS request.
Per the CORS spec, if you send a Content-Type
request header with a value that’s anything other than application/x-www-form-urlencoded
, multipart/form-data
, or text/plain
, then a CORS preflight OPTIONS
request is sent.
So, when you send a Content-Type
header with the value application/json
, it triggers the OPTIONS
request. But when you drop the setRequestHeader()
line, then no custom Content-Type
header is sent (and no other custom “author” headers), so in the case, no CORS preflight is required, so no OPTIONS
request is made.
Upvotes: 3