Cross-Origin Resource Sharing for JSON and RAILS

CORS (Cross-Origin Resource Sharing) is a protocol built on-top of HTTP for allowing Javascript on a page originating from one site to access methods on another site.  This is the preferred method for allowing Javascript code to escape from its default Same-Origin
.  While the protocol has been around for a few years, and is built into all of the major browsers, the protocol does not seem to be widely documented.  Here are some experiences I’ve encountered while enabling access cross-origin access for JSON from a Rails server.


The Same-Origin policy restricts Javascript code to making Ajax calls to the site that its containing page came from.  For example, Javascript on webpage from is not permitted to make Ajax calls to a web-service at  This policy is a security measure to prevent unwitting visitors to a website from executing malicious code in their browsers.

For web-service implementors, this is an annoying restriction.  Allowing Javascript to access data from multiple sites would allow programmers to create browser-based mash-ups.

Server-side proxying is a traditional method for getting around the Same-Origin restriction for Ajax requests.  With proxying, the owner of implements a copy of the remote method that repeats the request from the page-owner’s own site.  The server on “” must process each remote method call by calling the method on “” so that it can return the results to the browser. -->

The great advantage of this method is that it works with any browser.  The drawback is that it imposes work on web-service subscribers that seems redundant.

Cross-Origin Resource Sharing

CORS is a protocol negotiated between a browser and a web-service that tells the browser that it is “OK” to execute Javascript code from a cross-domain call.  The specification covers “Simple” transactions and complex transactions that use a “Preflight” request.  Cross-origin JSON requests with non-standard headers are not “Simple” and require the “Preflight” request.

A great introduction to the CORS protocol appears here:

and  an example of the CORS transactions appears here.

The “Preflight” request is a new HTTP verb called OPTIONS.  In a browser implementing CORS, each cross-origin GET or POST request is preceded by an OPTIONS request that checks whether the GET or POST is OK.  If it is, the server must return some headers to allow the subsequent GET or POST.  This is actually a wonderful capability.  The server can allow or disallow remote access on a per-method basis, with access determined by HTTP referrer, IP or any other criterial.

The OPTIONS request contains Access-Control headers that are part of the CORS specification.  The response must reply to these headers to allow the subsequent GET or POST to proceed.

For example, an access to


would be preceeded by an OPTIONS method that looks like this.

Access-Control-Request-Method: GET

The server would respond with an empty response body of type
“text/plain” that contains headers allowing the request.

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Max-Age: 1728000
Content-Length: 0
Content-Type: text/plain

Custom Headers

If your application uses non-standard headers, you must take special steps to permit them or the browser will flag a CORS violation.   I ran into this restriction in the application I was writing.  Unfortunately, the security violation messages from the browser are obscure, and it took me a while to figure this out.

In our application, our Javascript client uses prototype.js to make Ajax calls.  Prototype adds the following headers to the request.

X-Requested-With: XMLHttpRequest
X-Prototype-Version: N.N.N.N

Our server must explicitly allow these headers in the CORS exchange or the browser will disallow the cross-origin request. The OPTIONS request will specify the headers it wants to add. Our OPTIONS/Response exchange looks like this.

Access-Control-Request-Method: GET
Access-Control-Request-Headers: X-Requested-With, X-Prototype-Version


Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: X-Requested-With, X-Prototype-Version
Access-Control-Max-Age: 1728000
Content-Length: 0
Content-Type: text/plain

CORS in Rails

I implemented the CORS protocol in a Rails application with just a couple of filter methods added to my controller. Here they are. (If you want to follow this technique, you’ll need to make sure your routes allow access to HTTP “:options” methods.)

before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers

# 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"

# 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'

The before_filter, cors_preflight_check is the last in my filter chain: earlier filters check for allowed access. If the request is for the OPTIONS method, then it short circuits the request, includes the necessary headers and returns a blank text body.

The after_filter, cors_set_access_control_headers, is for all requests that are returned by this controller. It includes the CORS headers for everything else.


CORS is implemented in all of the popular browsers, but client-side access seems to vary between IE and Safari/Chrome/Firefox. For me, it was interesting to see how server-side access control is not too different from what Adobe does for server-side policy files. It would be nice if these access controls could be unified, but I’m just happy to have them.

38 thoughts on “Cross-Origin Resource Sharing for JSON and RAILS”

  1. You’re a lifesaver! I’ve spent the last day trying to get a simple javascript call to talk to my rails server. Since it’s served from a file and not the server itself, it seems to be hitting the browser’s same-origin constraints (even though it shouldn’t). I was finally reading up on CORS and this solved the problem.

    A couple of comments:
    – There’s an extra space between “:” and “options” which should be :options
    – Would it still be valid to restrict the after_filter to only add those fields in OPTIONS requests, not in all requests?

  2. Glad it helped! I wrote up what I learned hoping it would help someone else someday.

    Interesting how you stumbled upon the CORS machinery of your browser. Indeed, Javascript executing on a page served from the filesystem making an AJAX call to a server *is* a cross-origin request! I had a similar revelation.

    – I fixed the :options spacing. Thanks.

    – I believe the after_filter needs to apply the headers to both the OPTIONS (pre-flight request) and the corresponding GET or POST (the “actual” request that follows the pre-flight request). At least, that’s my best understanding.

  3. Hey there,
    thanks a lot for your article! But since I’m a Rails beginner I wonder how I actual manage this: “If you want to follow this technique, you’ll need to make sure your routes allow access to HTTP “:options” methods.”

    Google doesn’t find anything useful, so it would be great if you could give me a direction where I can look :-)

  4. Hi David –

    It seems that quite a number of people end up at this article looking for information on the OPTIONS method and Rails routing. If you’re using Rails’ ReST-ful routes, then the only methods allowed are GET, POST, PUT and DELETE. If you don’t use ReST-ful routes, then you have the flexibility to respond to any method.

    I don’t have an article about this subject myself. Perhaps someone can post a good link here about how they allowed the OPTIONS method in the Rails routes.

  5. Hey Tom,

    I was able to find a way to do it with ReST-ful routes, it’s a very dirty workaround but currently the only thing working for me. (My RoR mentor suggested to write a little piece of rack middleware to pick out the OPTIONS requests)

    I added following to the end of my routes.rb
    controller :tracks do
    match ‘tracks’ => :options_response

    This works because rails obviously checks the routes from top to bottom, and if nothing matches the last rule, matching everything, does the job. Dirty, but well, atleast it works!

  6. I’m using Rails 3, and I was able to add the code snippet above to the “app/controllers/application_controller.rb” super class in order to add the customer headers.

    I have no idea what “If you want to follow this technique, you’ll need to make sure your routes allow access to HTTP “:options” methods.” means, as I’m new to Rails, but for me it was as easy cake.

    Nice work either way Tom!

  7. Thanks Tom!! Great post! I was able to use this in getting a Rails app (port 3000) to talk to a sinatra app (port 9292), thanks again!

    The only thing I modified was I added X-CSRF-Token to the headers[‘Access-Control-Allow-Headers’], so mine looks like this:

    headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'

  8. Hi Tom,

    Thanks for your article.. Im trying to get my GWT application to interact with my Rails backend but am facing SOP issues. I found your great article that has helped me perform DELETE requests successfully… (havent tested Post)

    But there seems to be 2 issues im facing.

    1) Even when the delete completes successfully, the response code i get in my Google Web Toolkit application is zero and i dont know why… it should give a successful code (302 in my case). is there a reason why the 0 status code comes through? Im using request builder to make the request…

    2) I cant seem to use the GET method. It appears that when i make a GET request to rails, rails doesnt see it as an initial OPTIONS request and straight tries to use GET which obviously fails due to SOP.

    Do you have any idea whats going wrong?

  9. I know the intention of this post is to outline how to do it within Rails, but if you have control over your web server and want to enable this across your entire app (not a specific action/controller, etc), you can handle it there instead. We set it up in nginx with the following:

    location / {
    # For CORS
    if ($request_method = OPTIONS ) {
    add_header Access-Control-Allow-Origin “http://localhost”;
    add_header Access-Control-Allow-Methods “GET, OPTIONS”;
    add_header Access-Control-Allow-Headers “Authorization”;
    add_header Access-Control-Allow-Credentials “true”;
    add_header Content-Length 0;
    add_header Content-Type text/plain;
    return 200;
    …other awesome nginx stuff here

    So, this will allow GET or OPTIONS requests from only http://localhost…(we needed the Authorization/Credentials stuff b/c we were doing Basic Auth in our app as well).

    This removes all the code changes in your Rails app, but also exposes the entire application…which may or may not be what you want. Anyway, just another way to consider.

  10. Thanks for this.

    For the guys using RESTful Rails routes, I got past this by placing a catch-all with a method contraint at the end which redirected to an empty method, which just rendered an empty string.

    In my routes.rb

    match "*path" => "api#xss_options_request", :constraints => {:method => "OPTIONS"}

    And in the APIController

    def xss_options_request
    render :text => ""

  11. From Spine js documentation

    CORs Rails integration

    Let’s create a cor method, which will add some of the request access control headers to the request’s response.

    Add the following to app/application_controller.rb:

    before_filter :cor

    def cor
    headers["Access-Control-Allow-Origin"] = ""
    headers["Access-Control-Allow-Methods"] = %w{GET POST PUT DELETE}.join(",")
    headers["Access-Control-Allow-Headers"] = %w{Origin Accept Content-Type X-Requested-With X-CSRF-Token}.join(",")
    head(:ok) if request.request_method == "OPTIONS"

    Although Access-Control-Allow-Origin takes a wildcard, I highly recommend not using it as it opens up your app to all sorts of CSRF attacks. Using a whitelist is much better and more secure.

    The Access-Control-Allow-Headers section is important, especially the X-Requested-With header. Rails doesn’t like it if you send Ajax requests to it without this header, and ignores the request’s Accept header, returning HTML when it should in fact return JSON.

    It’s worth noting that jQuery doesn’t add this header to cross domain requests by default. This is an issue that Spine solves internally, but if you’re using plain jQuery for CORs, you’ll need to specify the header manually.

    headers: {"X-Requested-With": "XMLHttpRequest"}

    Some browsers send an options request to the server first, to make sure the correct access headers are set. You’ll need to catch this in Rails, returning a 200 status with the correct headers. To do this, add the following to your application’s config/routes.rb file:

    match '*all' => 'application#cor', :constraints => {:method => 'OPTIONS'}

    That’s it, you’re all set up for Cross Origin Requests with Spine!

  12. I want to ask one question… i am developing an application which involve JavaScript as client side and node js as server side… i am calling cross domain ajax call with the method ‘DELETE’ but which is not working, its request the Preflight and then a GET request.. is there any way on JavaScript side to send delete request with jsonp datatype …. if not and what shall i need to do on server side (nodejs)
    my sample code

    url: "" +id +"/design_details.json",
    contentType:"application/json; charset=utf-8",
    success:function (result) {


  13. This discussion thread concerns itself mostly with Rails, and not NodeJS. That being said, I don’t think that “jsonp” is an option for what you are trying to do. Good luck!

  14. I tried the solution… get request works but post still suffers from that Allow-access-allow-origin
    I can see

    Incoming Headers:
    Origin: http://localhost

    in unicorn_err log

    Any idea..

  15. My bad …
    Actually I am seeing

    Origin http://localhost is not allowed by Access-Control-Allow-Origin.

    in chrome when I do post request to my remote app.

    and in unicorn_err.log

    Incoming Headers:
    Origin: http://localhost

    appears….are the correct headers not set !!

  16. Hmm is anyone else experiencing problems with the pictures
    on this blog loading? I’m trying to find out if its a problem on my end or if it’s the blog.
    Any responses would be greatly appreciated.

  17. Forgive my bad english,

    I’m currently using Rails4 as API and ExtJS as front-end framework(rest proxy).

    I follow your instruction and work like charm with GET record from server, but it’s seem i missed something since i use other method like POST, PUT or DESTROY, it doesn’t send the actual request after get respond code 200.

    ExtJS store:
    proxy: {
    type: ‘rest’,
    noCache: false,
    reader: {
    type: ‘json’,
    root: ‘sections’,
    successProperty: ‘success’
    writer: {
    type: ‘json’
    api: {
    read: ‘http://localhost:3000/sections’,
    create: ‘http://localhost:3000/sections’,
    update: ‘http://localhost:3000/sections’,
    destroy: ‘http://localhost:3000/sections’

    Rails server respond:

    Started OPTIONS “/sections?_dc=1394515993496&page=1&start=0&limit=25” for at 2014-03-11 12:33:13 +0700
    ActiveRecord::SchemaMigration Load (0.3ms) SELECT `schema_migrations`.* FROM `schema_migrations`
    Processing by ApplicationController#cors_preflight_check as */*
    Parameters: {“_dc”=>”1394515993496”, “page”=>”1”, “start”=>”0”, “limit”=>”25”, “all”=>”sections”}
    Rendered text template (0.1ms)
    Filter chain halted as :cors_preflight_check rendered or redirected
    Completed 200 OK in 78ms (Views: 17.6ms | ActiveRecord: 0.0ms)

    Started GET “/sections?_dc=1394515993496&page=1&start=0&limit=25” for at 2014-03-11 12:33:14 +0700
    Processing by SectionsController#index as */*
    Parameters: {“_dc”=>”1394515993496”, “page”=>”1”, “start”=>”0”, “limit”=>”25”}
    Section Load (0.0ms) SELECT A.*, AS Page FROM sections AS A LEFT JOIN pages AS B ON
    Completed 200 OK in 112ms (Views: 6.3ms | ActiveRecord: 5.6ms)

    Started OPTIONS “/sections/3?_dc=1394516006492” for at 2014-03-11 12:33:26 +0700
    Processing by ApplicationController#cors_preflight_check as */*
    Parameters: {“_dc”=>”1394516006492”, “all”=>”sections/3”}
    Rendered text template (0.0ms)
    Filter chain halted as :cors_preflight_check rendered or redirected
    Completed 200 OK in 6ms (Views: 5.5ms | ActiveRecord: 0.0ms)

    Any Idea will appreciate

  18. In rails 4 I had to exchange:
    if request.method == :options
    if request.method == 'OPTIONS'

  19. I’m getting a different error that do not have relation with this article but I need some help if any body have idea on that.

    Error is “Can’t verify CSRF token authenticity”. I found some articles related to this error but nothing worked in my problem.

    I’m getting this error while updating a record on IE only. This do not happen application wide but occur for specific action of a controller. While submitting a form (action is used to update a record in DB), received error “Can’t verify CSRF token authenticity”. When checked form in IE, creates hidden field for “authenticity_token” but submitting the form redirect me to root URL of app (in application controller) and gives same error in console.

    I tried to skip validating the CSRF token authenticity for this specific action but then form data is not submitted (write skip_before_action :verify_authenticity_token, only: [:] in my controller). This problem is coming after my app migrated from Rails 3.2 to 4.2.

    Any one have idea on this issue?

  20. I seem to have set up the headers correctly, because the request is successfully reaching my controller where I have a debugger waiting. But there’s no accessible content from the request itself. Does that mean that I’m not in fact setting the headers correctly?

  21. You might be confusing the pre-flight OPTIONS request with the GET/POST request itself. What is the ‘method’ of the request you are seeing? (recall that the OPTIONS method will have an empty body.)

Leave a Reply

Your email address will not be published. Required fields are marked *