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:
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 allowHTTP GET
andHTTP POST
calls from example.com to your Rails application. You can also configure separate CORS rules per endpoint. For example:
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 allowHTTP 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.
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.