Rails CORS Guide:
What It Is and How to Enable It

stackhawk

StackHawk|April 30, 2021

Let’s see what cross-origin resource sharing (CORS) is, how to check if you are getting blocked by CORS and how to implement CORS in Rails.

Cross-origin resource sharing (CORS) is a great security mechanism that every web application developer should know about. Whenever you’ll be exposing some application programming interface to the internet, make sure to implement CORS.

How Does CORS Work?

In short, CORS is an HTTP-header based security mechanism that defines who’s allowed to interact with your API. CORS is built into all modern web browsers, so in this case the “client” is a front-end of the application. 

In the most simple scenario, CORS will block all requests from a different origin than your API. “Origin” in this case is the combination of protocol, domain, and port. If any of these three will be different between the front end and your Rails application, then CORS won’t allow the client to connect to the API. 

So, for example, if your front end is running at https://example.com:443 and your Rails application is running at https://example.com:3000, then CORS will block the connections from the front end to the Rails API. CORS will do so even if they both run on the same server. 

In this post, you’ll learn how to check if CORS is blocking you and how to implement it properly in Rails. 

How Do I Know If CORS Is Blocking Me?

First things first. The fact that CORS is blocking you may not always be obvious. You won’t see “blocked by CORS” on your website. 

Depending on how your front end was built, you may either just miss some data or you may get some general error. However, you’ll see that exact message— “blocked by CORS”—in the web browser developer console. 

So, to find out if that is indeed a CORS issue, open your web browser DevTools and go to Console. There, you should see a message similar to this: 

"Access to XMLHttpRequest at (...) from origin (...) has been blocked by CORS policy"

If you see that, then you’re definitely dealing with the wrong CORS configuration. 

How can you solve it? Read on… 

Rails CORS

Fortunately, configuring CORS in the Rails application is pretty simple. Before we dive into that, however, you’ll need to clarify one thing. You’ll need to configure CORS only when you’re using Rails as an API. If you build traditional Ruby on Rails monolith applications, you won’t need to do that. 

Why? As you learned at the beginning of this post, CORS blocks calls from different origins. In the case of a monolith Ruby on Rails application, both front end and back end are at the same origin. 

But when you use the Rails application only as an API, then you’ll have another application running as a front end. That’s when you’ll need to configure Rails to allow that front-end application to connect. 

Using rack-cors

The easiest way to configure CORS on your Rails app is to use rack-cors gem. You can install it like any other gem, by executing: 

gem install rack-cors

or by adding the following line into your Gemfile: 

gem 'rack-cors'

Next, you need to provide the configuration for the gem. You need to inform Rails which origin it should allow. To do that, you need to create a new initializer for your application. 

The content of the config/initializers/cors.rb should be the following: 

Ruby
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://example.com:80'
    resource '*', headers: :any, methods: [:get, :post]
  end
end

The above configuration will allow HTTP GET and HTTP POST calls from example.com to your Rails application. You can also configure separate CORS rules per endpoint. For example: 

Ruby
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://example.com:80'
    resource '/orders',
      :headers => :any,
      :methods => [:post]
    resource '/users',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

This configuration will only allow HTTP POST calls to /order endpoint and all HTTP methods to any other endpoint. 

You need to pay close attention to the origins parameter. Remember that CORS needs to match protocol, domain, and port. You’ll need to specify all three correctly. So if you put http://example.com:80, but the call will come from http://example.com:8080 or https://example.com:443, then you’ll still get blocked. 

You may also find articles with a recommendation to specify origins ‘*’. This would indeed allow you to “solve the CORS problem.” But in general, this allows anyone on the internet to connect to your application programming interface!

Misconfiguration

You may also find articles with a recommendation to specify origins ‘*’. This would indeed allow you to “solve the CORS problem.” But in general, this allows anyone on the internet to connect to your application programming interface! So it’s a security risk. 

Unless your API should be by design open to anyone, you really shouldn’t set wildcard as an origin. You also need to be careful when using regex for origins parameter. Let’s say you want to match a few domains—for example, yourdomain.com, yourdomain.net, yourdomain.biz, and so on. In that case, you need to make sure that your regex isn’t too inclusive. So, you can try something like this: 

/http:\/\/example\.com/

This would not only allow all your domains but also permit anything else, like example.com.anydomain.com. And that could lead to another security risk. 

rack-cors also supports a few additional options. For any endpoint, you can configure the following: 

  • methods—As you’ve seen above, methods allows you to specify which HTTP methods you’ll allow.

  • headers—Here, you can specify which headers you’ll allow for the endpoint. Use :any to allow any headers.

  • credentials—This parameter can take true or false values. It’s necessary when you’re making HTTP calls to the API with credentials included. Keep in mind that this option isn’t allowed when you set the origin as ‘*’. But as explained earlier in the post, you shouldn’t do that anyway unless you really have a use case for it.

  • if—This option allows you to control when CORS will activate. You can create a normal Ruby if…else condition here, for example, to disable CORS on your development environment.

There are a few more options you can use, but they’re less common. You can check the rack-cors github page for the full documentation. 

Middleware Positioning and Static Files

Another thing to keep in mind is the positioning of the Rack::Cors middleware. You need to make sure that you implement CORS middleware above any other middleware. Putting it after certain caching or authentication middlewares can lead to issues. 

Last but not least: If you try to serve static files (such as assets), be mindful that these aren’t usually served by Rails. Instead, they’re served by the external web server. In such a case, you have two options. You can enable serving static assets via Rails (which isn’t recommended), or you can implement CORS separately in your web browser. 

Add Automated Security Testing to Your Pipeline for Free

Configuring With CORS and Beyond

As you can see, once you understand CORS, it’s not that difficult to use. It’s actually a very good and easy-to-implement security mechanism. If you’ll take a moment to properly configure it (and by properly, we mean not setting wildcard origins), you’ll be doing yourself a favor. 

In this post, you got a brief explanation of what CORS is. But this post was focused on CORS implementation in Rails applications.

And once you secure your Rails application with CORS, it’s time to start thinking about other security improvements. For example, what about SQL injection protection? You can read about how to prevent SQL injection attacks in Rails applications

This post was written by Dawid Ziolkowski. Dawid has 10 years of experience as a Network/System Engineer at the beginning, DevOps in between, Cloud Native Engineer recently. He’s worked for an IT outsourcing company, a research institute, telco, a hosting company, and a consultancy company, so he’s gathered a lot of knowledge from different perspectives. Nowadays he’s helping companies move to cloud and/or redesign their infrastructure for a more Cloud Native approach.


StackHawk  |  April 30, 2021