How do I configure `Access-Control-Allow-Origin` with rails, nginx and passenger?

14,056

Solution 1

The Server line made me think that perhaps the assets are not being handled by Rails, but rather by nginx:

enter image description here

This means that the headers must be added by nginx, not Rails, and therefore we need to configure nginx. It turns out that the ability to configure nginx is possible as of Passenger 4.0.39 - (here is the corresponding Git diff). The corresponding documentation is available in Passenger Standalone, under Advanced configuration.

An important note in the documentation: The original configuration template file may change from time to time, e.g. because new features are introduced into Phusion Passenger. If your configuration template file does not contain the required changes, then these new features may not work properly. In the worst case, Standalone might even malfunction. Therefore, every time you upgrade Phusion Passenger, you should check whether the original configuration template file has changed, and merge back any changes into your own file.

With respect to that note, in addition to the customizable copy of the configuration file, create an "original" copy that you can diff whenever you upgrade Passenger.

bash

cp $(passenger-config about resourcesdir)/templates/standalone/config.erb config/nginx.conf.erb
cp config/nginx.conf.erb config/nginx.conf.erb.original

Next, add --nginx-config-template config/nginx.conf.erb to the web line in Procfile.

Procfile

web: bundle exec passenger start -p $PORT --max-pool-size 3 --nginx-config-template config/nginx.conf.erb

config/nginx.conf.erb

Next, edit the configuration file config/nginx.conf.erb by finding a block that looks as follows:

    location @static_asset {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
        add_header ETag "";
    }

...and add the two Access-Control lines:

    location @static_asset {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
        add_header ETag "";
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Request-Method *;
    }

That's it. This will work in production, but not in development, due to config.assets differences between the two.

the config diff

The diff should not return anything now, but if any future updates to passenger include a change to this file, you will know.

diff $(passenger-config about resourcesdir)/templates/standalone/config.erb config/nginx.conf.erb.original

nginx documentation

future improvements

  • restrict the Allow-Origin
  • restrict the Request-Method
  • restrict both headers to just fonts

Solution 2

YES! Finally.

user664833's answer above is great, except I couldn't find my Passenger config file to edit.

Thomas Nye's answer here gives the full file to create at config/nginx.conf.erb:

    ##########################################################################
#  Passenger Standalone is built on the same technology that powers
#  Passenger for Nginx, so any configuration option supported by Passenger
#  for Nginx can be applied to Passenger Standalone as well. You can do
#  this by direct editing the Nginx configuration template that is used by
#  Passenger Standalone.
#
#  This file is the original template. DO NOT EDIT THIS FILE DIRECTLY.
#  Instead, make a copy of this file and pass the `--nginx-config-template`
#  parameter to Passenger Standalone.
#
#  Learn more about using the Nginx configuration template at:
#  https://www.phusionpassenger.com/library/config/standalone/intro.html#nginx-configuration-template
#
#  *** NOTE ***
#  If you customize the template file, make sure you keep an eye on the
#  original template file and merge any changes. New Phusion Passenger
#  features may require changes to the template file.
##############################################################

<%= include_passenger_internal_template('global.erb') %>

worker_processes 1;
events {
    worker_connections 4096;
}

http {
    <%= include_passenger_internal_template('http.erb', 4) %>

    ### BEGIN your own configuration options ###
    # This is a good place to put your own config
    # options. Note that your options must not
    # conflict with the ones Passenger already sets.
    # Learn more at:
    # https://www.phusionpassenger.com/library/config/standalone/intro.html#nginx-configuration-template

    ### END your own configuration options ###

    default_type application/octet-stream;
    types_hash_max_size 2048;
    server_names_hash_bucket_size 64;
    client_max_body_size 1024m;
    access_log off;
    keepalive_timeout 60;
    underscores_in_headers on;
    gzip on;
    gzip_comp_level 3;
    gzip_min_length 150;
    gzip_proxied any;
    gzip_types text/plain text/css text/json text/javascript
        application/javascript application/x-javascript application/json
        application/rss+xml application/vnd.ms-fontobject application/x-font-ttf
        application/xml font/opentype image/svg+xml text/xml;

    <% if @app_finder.multi_mode? %>
        # Default server entry for mass deployment mode.
        server {
            <%= include_passenger_internal_template('mass_deployment_default_server.erb', 12) %>
        }
    <% end %>

    <% for app in @apps %>
    server {
        <%= include_passenger_internal_template('server.erb', 8, true, binding) %>
        <%# <%= include_passenger_internal_template('rails_asset_pipeline.erb', 8, false) %1> %>

        ### BEGIN your own configuration options ###
        # This is a good place to put your own config
        # options. Note that your options must not
        # conflict with the ones Passenger already sets.
        # Learn more at:
        # https://www.phusionpassenger.com/library/config/standalone/intro.html#nginx-configuration-template
        # Rails asset pipeline support.
        location ~ "^/assets/.+-([0-9a-f]{32}|[0-9a-f]{64})\..+" {
            error_page 490 = @static_asset;
            error_page 491 = @dynamic_request;
            recursive_error_pages on;

            if (-f $request_filename) {
                return 490;
            }
            if (!-f $request_filename) {
                return 491;
            }
        }
        location @static_asset {
            gzip_static on;
            expires max;
            add_header Cache-Control public;
            add_header ETag "";
            if ($http_origin ~* ((https?:\/\/[^\/]*\.herokuapp\.com(:[0-9]+)?))) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, HEAD';
                add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With';
            }
        }
        location @dynamic_request {
            passenger_enabled on;
        }

        ### END your own configuration options ###
    }
    passenger_pre_start <%= listen_url(app) %>;
    <% end %>

    <%= include_passenger_internal_template('footer.erb', 4) %>
}

Procfile should include the line:

web: bundle exec passenger start -p $PORT --max-pool-size 3 --nginx-config-template config/nginx.conf.erb

You'll also need to configure you Cloudfront CDN that is serving the assets as per Guapolo's answer

Configure CORS Cloudfront

In your distribution that is giving you the CORS grief, go to the behaviours tab, and a new behaviour, selecting the path to the asset, i.e. /assets/icons.ttf and whitelist 'Origin' as per the above image.

You may also need, in you distribution, to 'invalidate' the old cached resource, in the invalidations tab, i.e. put in the full asset and cached name from your inspector, and invalidate. Once that has processed, deploy the app with the config and restart heroku. You'll need to open inspector and 'empty cache and hard reload' the page.

Hopefully that works - it sounds like the passenger config changes every now and then, so we may find this breaks and the answer will need to be updated to reflect the new config.

Solution 3

I'm not sure that it is answer, but it looks like you also could try the easiest way with using after_filter with:

headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
headers['Access-Control-Request-Method'] = '*'
headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
...

Solution 4

No need for headers setting in nginx. Rails does the job.

# initializers/cors.rb

# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
             headers: :any,
             methods: %i[get post put patch delete options head]
  end
end

# nginx.conf
...

upstream rails_app {
    server 127.0.0.1:3000;
}

...

server {
  listen 80;
  server_name api.example.com;

  # send non-static file requests to the app server  
  location / {    
    try_files $uri @rails;
  }

  location @rails {
    proxy_set_header  X-Real-IP  $remote_addr;    
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;         
    proxy_set_header Host $http_host;    
    proxy_redirect off;    
    proxy_pass http://rails_app;  
  }

}
Share:
14,056
user664833
Author by

user664833

Updated on June 15, 2022

Comments

  • user664833
    user664833 about 2 years

    I cannot get Access-Control-Allow-Origin to show up in Chrome - my ultimate goal is to configure CORS for fonts with Rails, so it works in production with CloudFront. For now though, I just want to get it to work in development. I can see the header via curl, but not Chrome.

    I am using Rails 4.0, and I have tried all of the following...

    I have configured Gemfile and application.rb as per the rack-cors example for rails 4:

    Gemfile

    gem 'rack-cors', '~> 0.2.9', require: 'rack/cors'
    

    config/application.rb

    config.middleware.insert_before 'ActionDispatch::Static', 'Rack::Cors' do
        allow do
            origins '*'
            resource '*',
                :headers => :any,
                :methods => [:get, :options, :head]
        end
    end
    

    rails console

    2.0.0-p481 :001 > Rails.env
     => "development"
    2.0.0-p481 :002 > Hello::Application.config.serve_static_assets
     => true
    

    bash

    curl -i http://localhost:5000/assets/OpenSans-Regular-webfont.woff
    
    Content-Type: application/font-woff
    Content-Length: 22660
    Connection: keep-alive
    Status: 200 OK
    Cache-Control: public, must-revalidate
    Last-Modified: Wed, 30 Apr 2014 23:51:57 GMT
    ETag: "467b34801137bd4031e139839ad86370"
    X-Request-Id: c4b07b4d-1c43-44ea-9565-dfda66378f98
    X-Runtime: 0.046007
    X-Powered-By: Phusion Passenger 4.0.50
    Date: Sat, 20 Sep 2014 04:39:38 UTC
    Server: nginx/1.6.1 + Phusion Passenger 4.0.50
    
    curl -i -H "Origin: http://localhost:5000" http://localhost:5000/assets/OpenSans-Regular-webfont.woff
    
    Content-Type: application/font-woff
    Content-Length: 22660
    Connection: keep-alive
    Status: 200 OK
    Cache-Control: public, must-revalidate
    Last-Modified: Wed, 30 Apr 2014 23:51:57 GMT
    ETag: "467b34801137bd4031e139839ad86370"
    Access-Control-Allow-Origin: http://localhost:5000   # adding
    Access-Control-Allow-Methods: GET, OPTIONS, HEAD     # -H
    Access-Control-Max-Age: 1728000                      # produced
    Access-Control-Allow-Credentials: true               # these
    Vary: Origin                                         # headers
    X-Request-Id: b9666f30-416d-4b5b-946a-bdd432bc191c
    X-Runtime: 0.050420
    X-Powered-By: Phusion Passenger 4.0.50
    Date: Sat, 20 Sep 2014 03:45:30 UTC
    Server: nginx/1.6.1 + Phusion Passenger 4.0.50
    

    Chrome (v37) Developer Tools > Network > OpenSans-Regular-webfont.woff > Headers > Response Headers

    HTTP/1.1 304 Not Modified
    Connection: keep-alive
    Status: 304 Not Modified
    Cache-Control: no-cache
    X-Request-Id: ac153b8c-e0cb-489d-94dd-90aacc10d715
    X-Runtime: 0.116511
    X-Powered-By: Phusion Passenger 4.0.50
    Date: Sat, 20 Sep 2014 03:41:53 UTC
    Server: nginx/1.6.1 + Phusion Passenger 4.0.50
    

    I also tried the following alternatives, as per various sources:

    config.middleware.insert_before 'ActionDispatch::Static', 'Rack::Cors' do
    config.middleware.insert_after Rails::Rack::Logger, Rack::Cors do
    config.middleware.insert_before Warden::Manager, Rack::Cors do
    config.middleware.insert 0, Rack::Cors do
    config.middleware.use Rack::Cors do
    

    I also tried the following to applications.rb, as per How to Display FontAwesome in Firefox Using Rails and CloudFront:

    config.assets.header_rules = {
      :global => {'Cache-Control' => 'public, max-age=31536000'},
      :fonts  => {'Access-Control-Allow-Origin' => '*'}
    }
    

    I also tried the following in config.ru, as per CloudFront CDN with Rails on Heroku

    require 'rack/cors'
    use Rack::Cors do
        allow do
            origins '*'
            resource '*', :headers => :any, :methods => :get 
        end 
    end
    

    bundle exec rake middleware

    use Rack::Cors
    use Rack::Sendfile
    use ActionDispatch::Static
    use Rack::Lock
    use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f9ec21590b0>
    use Rack::Runtime
    use Rack::MethodOverride
    use ActionDispatch::RequestId
    use Rails::Rack::Logger
    use ActionDispatch::ShowExceptions
    use ActionDispatch::DebugExceptions
    use ActionDispatch::RemoteIp
    use ActionDispatch::Reloader
    use ActionDispatch::Callbacks
    use ActiveRecord::Migration::CheckPending
    use ActiveRecord::ConnectionAdapters::ConnectionManagement
    use ActiveRecord::QueryCache
    use ActionDispatch::Cookies
    use ActionDispatch::Session::CookieStore
    use ActionDispatch::Flash
    use ActionDispatch::ParamsParser
    use Rack::Head
    use Rack::ConditionalGet
    use Rack::ETag
    use Warden::Manager
    use OmniAuth::Strategies::Facebook
    run Hello::Application.routes
    

    I also tried font_assets to no avail.