My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
I'm working on a website analytics tool, and in pursuit of that goal, I wanted to POST some data from a series of websites to the one that's doing the tracking. If you've tried to do that before, you've run afoul of the same origin policy, as I did, so you'll need to specify how your application handles Cross-Origin Resource Sharing.

I won't go into all the details about why that is the case - for that you can read the Wikipedia links above. Instead, I'm going to show a short example of how I handled this in my Rails 3 app.

First, you need to specify a route that will handle an HTTP OPTIONS method request.

# config/routes.rb
  resources :web_hits, :only=>[:create]
  match '/web_hits', :controller => 'web_hits', :action => 'options', :constraints => {:method => 'OPTIONS'}
  

Since this controller only handles incoming requests to create a web hit resource, I've only specified the POST method on the it (which will run the create method in the controller). However, before the browser will send the POST to the tracking website, it first sends an OPTIONS request to see if it can do the POST. The second line specifies the route for that: it will go to my web_hits_controller and use the action options.

Next, we'll look at the controller.

# controllers/web_hits_controller.rb
  class WebHitsController < ApplicationController
    def create
      if access_allowed?
        set_access_control_headers
        head :created
      else
        head :forbidden
      end
    end

    def options
      if access_allowed?
        set_access_control_headers
        head :ok
      else
        head :forbidden
      end
    end

    private
    def set_access_control_headers 
      headers['Access-Control-Allow-Origin'] = request.env['HTTP_ORIGIN']
      headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
      headers['Access-Control-Max-Age'] = '1000'
      headers['Access-Control-Allow-Headers'] = '*,x-requested-with'
    end


    def access_allowed?
      allowed_sites = [request.env['HTTP_ORIGIN']] #you might query the DB or something, this is just an example
      return allowed_sites.include?(request.env['HTTP_ORIGIN'])    
    end  
  end
  

The key above is checking whether or not the request should be allowed. Here I've set access_allowed? to always return true, but you could have some checks in there that inspect the request to determine if you want to allow it or not. If you do, set the headers and respond appropriately. Since I don't need to really return a response, I'm only returning the headers indicating success or access denied, but you could just as easily turn those head method calls into renders if you need to render some content.

A good resource that helped me figure this out was Cross-Origin Resource Sharing for JSON and RAILS. He didn't go into detail on restricting access nor routing though, so I felt like this would be a good addendum.

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!


Comments
Leave a comment

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. Anyway, just another way to consider.

Posted by tomkersten on Oct 28, 2011 at 11:31 AM UTC - 5 hrs

Thanks Tom!

Where would you put that code?

Posted by Sammy Larbi on Oct 28, 2011 at 11:59 AM UTC - 5 hrs

Oh. Sorry. You can throw that in your nginx config...inside the "server" block of one of your hosts. Here is my (working) config for one the site we rolled it out on (with domains anonymized, etc):

https://gist.github.com/1323477

We are requiring Basic Auth, so, you may not need the highlighted lines to make it work. Ping me @ tom at whitespur dot com if you have any thoughts/questions or tips on improving it...

Posted by tomkersten on Oct 28, 2011 at 03:39 PM UTC - 5 hrs

Awesome, thanks for the example Tom.

Posted by Sammy Larbi on Oct 31, 2011 at 02:11 PM UTC - 5 hrs

Awesome, thanks!

Posted by thermistor on Sep 18, 2012 at 12:13 PM UTC - 5 hrs

I tried this ... get request works but post still suffers from that Allow-access-allow-origin
I can see

Incoming Headers:
Origin: http://localhost
Access-Control-equest-Method:
Access-Control-Request-Headers:

in unicorn_err log

Any idea..

Posted by rohitrox on Apr 22, 2013 at 11:47 AM UTC - 5 hrs

This may not be it, but have you double-checked the headers you're setting to ensure they're the right ones? For example, you mention "Allow-access-allow-origin" but the header I think you want is "Access-Control-Allow-Origin"...

Hope that helps!

Posted by Sammy Larbi on Apr 22, 2013 at 12:42 PM UTC - 5 hrs

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
Access-Control-equest-Method:
Access-Control-Request-Headers:

appears....are the correct headers not set !!

Posted by rohitrox on Apr 22, 2013 at 12:48 PM UTC - 5 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)
Website

Comment:

Subcribe to this comment thread
Remember my details
Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and C++ (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (75)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (7)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner