Cloudfront CORS blocking fonts - ruby-on-rails

All the assets except for the fonts load nicely,
and whenever I go to my site I keep getting messages like this one:
Access to Font at
'https://xxxxxxxxxx.cloudfront.net/assets/fontawesome-webfont.woff2'
from origin 'https://example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://example.com' is therefore not allowed
access.
As you can see CURL command indicates that no headers are present.
curl -H "Origin: https://example.com" -I https://xxxxxxxxx.cloudfront.net/assets/fontawesome-webfont.woff2
HTTP/1.1 200 OK
Content-Length: 77160
Connection: keep-alive
Status: 200 OK
X-Rack-Cache: stale, valid, store
Cache-Control: public, must-revalidate
Date: Fri, 14 Apr 2017 08:01:26 GMT
X-Content-Digest: d6f48cba7d076fb6f2fd6ba993a75b9dc1ecbf0c
ETag: "2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe"
X-Runtime: 0.366713
X-Request-Id: 87c9d883-e443-4756-86f9-66b40ed573f6
X-Powered-By: Phusion Passenger Enterprise 5.1.2
Server: nginx/1.10.2 + Phusion Passenger 5.1.2
Via: 1.1 vegur, 1.1 f0eecbf6390179377707b707ebaa1e8b.cloudfront.net (CloudFront)
Age: 86645
Vary: Accept-Encoding
X-Cache: Hit from cloudfront
X-Amz-Cf-Id: FNjQGvROcAdqT6u6PaN3OgEE34mnSsixHNm6WqzWq2boWWYYzVmZPw==
Here's AWS Origin configuration
And this is the behaviour that includes the above origin:
I even included rack-cors to the initializers within the project for the purpose but with no luck.
if defined? Rack::Cors
Rails.configuration.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '/assets/*', headers: :any, methods: [:get, :post, :delete, :put, :patch, :options, :head], max_age: 0
end
end
end
Why is this so and how can I fix it? Thank you in advance.

As of version 5.0, Rails allows for setting custom HTTP Headers for assets and you don't have to add dependencies like the font_assets gem. In order to set Access-Control-Allow-Origin to your font, just add the following codde to config/environments/production.rb:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => '*'
}
Update on 07/25/2018:
The header value could also be a specific domain, like the following:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => 'https://www.example.org'
}

I think most common solution is to use font_assets gem. Configuration is pretty straightforward. In config/environments/production.rb add this
config.font_assets = "http://YOUR_ASSET_HOST"
Unfortunately I cannot explain this in more details, but that's what we use in our project and it works fine.

Related

rack-cors fails fine uploader preflight

I'm trying to use fine uploader from my react frontend, to upload to my rails 5 backend.
rack-cors is second in my middlewares list:
use Webpacker::DevServerProxy
use Rack::Cors
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Warden::Manager
run Project::Application.routes
When fine uploader tries to upload an image, I get in the console:
Failed to load http://localhost:5000/item/1374/image: Response to preflight
request doesn't pass access control check: No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin 'http://lvh.me:3000' is
therefore not allowed access.
I can see the preflight (options) http request, with the following data:
GENERAL
Request URL:http://localhost:5000/item/1374/image
Request Method:OPTIONS
Status Code:200 OK
Remote Address:127.0.0.1:5000
Referrer Policy:no-referrer-when-downgrade
RESPONSE HEADERS:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
REQUEST HEADERS:
OPTIONS /item/1374/image HTTP/1.1
Host: localhost:5000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: update
Origin: http://lvh.me:3000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Access-Control-Request-Headers: cache-control,x-requested-with
Accept: */*
DNT: 1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,it;q=0.8
this is the cors debug log of the same call:
{"Access-Control-Allow-Origin"=>"http://lvh.me:3000", "Access-Control-Allow-Methods"=>"GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD", "Access-Control-Expose-Headers"=>"access-token, expiry, token-type, uid, client, Access-Control-Allow-Origin", "Access-Control-Max-Age"=>"1728000", "Access-Control-Allow-Credentials"=>"true"}
Incoming Headers:
Origin: http://lvh.me:3000
Access-Control-Request-Method: update
Access-Control-Request-Headers: cache-control,x-requested-with
Preflight Headers:
Content-Type: text/plain
my config/initializers/cors.rb looks like:
Rails.application.config.middleware.insert_before 0, Rack::Cors, debug: true, logger: (-> { Rails.logger }) do
allow do
origins 'localhost:3000',
'http://localhost:3000',
'127.0.0.1:3000',
/\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/,
'http://lvh.me:3000'
resource '*',
headers: :any,
expose: %w[access-token expiry token-type uid client Access-Control-Allow-Origin],
credentials: true,
methods: %i[get post put patch delete options head]
end
end
I also tried with origin '*' and credentials: false, but got the same result.
I don't understand where the problem could be..
my fault, I was requesting an update, but was allowing only put and patch.
I changed the request to a put, and now I'm one step closer to walhalla.

Nginx Rails/Passenger is not serving gzipped asset files

Nginx 1.10.1 Rails 5.0.1. The asset pipeline is making both the zipped and native versions of the css and js files, but only the uncompressed is served to the browser. I can see both versions in public/assets and I can use curl to retrieve the zipped version by appending '.gz' to the css/js asset url delivered to me.
I am using a CDN (AWS CloudFront) but have tested without the CDN and anyway it should still point me to the zipped version, right?
nginx has --with-http_gzip_static_module. Using this answer as a guide, my nginx config (edited) has:
http {
server {
listen 80;
server_name idoimaging.com www.idoimaging.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name idoimaging.com www.idoimaging.com;
root /var/www/idoimaging/current/public;
location ~ ^/(assets)/ {
gzip_static on;
}
}
}
I've also tried /assets/ as the regex in location. In my production.rb:
# Have also tried setting this to false
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.middleware.insert_before(Rack::Sendfile, Rack::Deflater)
config.assets.compress = true
config.assets.js_compressor = Uglifier.new(mangle: false)
I'm testing with Chrome with cache disabled, also for speed with curl https://idoimaging.com | grep assets which I believe should have the same behaviour as the browser?
I don't know why nginx will not serve the gzip'ed js/css asset files when they are present.
EDIT: I've also seen this guide that takes another approach: Use Rails as the static asset server instead of nginx. Would this be a better approach?
Given that Rails is producing the gzipped assets, we can be confident that the issue resides solely with nginx. So let's focus there!
I believe Nginx is faster at serving static assets than Rails is, so I'd stay away from using it as a static asset server in any event.
Now looking at the URL you provided (https://idoimaging.com), your server appears to be providing gzipped files. So the issue is just with your testing approach (assuming this is the correct URL, and you have not changed the server configuration since this post).
Your curl command doesn't include the Accept-Encoding: gzip header, which tells the server your client is capable of handling gzipped files. Without it, Nginx will serve the uncompressed versions. You can see the difference in commands and outputs in this gist. The difference is in the Content-Length and Content-Encoding response headers.
If you're seeing something different, let me know!
Edit 1
That's odd, CloudFront appears to have cached redirects for both your CSS and your JS.
richardseviora:Richards-MacBook-Pro#~> curl "https://cdn.idoimaging.com/assets/application-0cd41e63d35c1e5a7ab76ded23fbaf2ef1d1b786144134a80a1dfa9c765cff0d.css" -I -H "accept-encoding: gzip"
HTTP/1.1 301 Moved Permanently
Content-Type: text/html
Content-Length: 185
Connection: keep-alive
Server: nginx/1.10.1
Date: Thu, 23 Feb 2017 03:30:49 GMT
Location: https://idoimaging.com/assets/application-0cd41e63d35c1e5a7ab76ded23fbaf2ef1d1b786144134a80a1dfa9c765cff0d.css
Age: 942
X-Cache: Hit from cloudfront
Via: 1.1 d8b73f8fefd106d5c95f11977e132c46.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ao8PwibmSj1JhmfbmuNfC2gYi9x-RTcCrJDAqLWAUIyOjP_3qYTGQA==
# It should look like this instead.
richardseviora:Richards-MacBook-Pro#~> curl -I -H "accept-encoding: gzip" "http://cdn.sweatrecord.com/assets/application-b932da0ddcf53d3650da5135b083224e863b349c784f3d1e3ca992b36ce3e31d.css"
HTTP/1.1 200 OK
Content-Type: text/css
Connection: keep-alive
Accept-Ranges: bytes
Content-Encoding: gzip
Date: Thu, 23 Feb 2017 03:50:13 GMT
Last-Modified: Mon, 30 Jan 2017 16:29:44 GMT
Server: Apache
Vary: Accept-Encoding,Origin
X-Cache: Miss from cloudfront
Via: 1.1 8b5947aba7280333032d4dcdd80b3489.cloudfront.net (CloudFront)
X-Amz-Cf-Id: FN9FyKl0RCpNTTqBwb0WyQhbDd-rEyyQ05eCtaFCD8YaH_FtjG7Q8Q==
This is Nginx issue, but I'm not sure where exactly because CloudFront will cache 301s.

Cloudfront CORS issue serving fonts on Rails application

I keep receiving this error message from the console when visiting my website:
font from origin 'https://xxx.cloudfront.net' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.example.com' is therefore not allowed access.
I've tried everything:
I've installed the font_assets gem
configured the application.rb file
config.font_assets.origin = 'http://example.com'
Whitelisted Headers on Cloudfront as explained in this article to
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
But nothing, zero, nada.
I'm using Rails 4.1 on Heroku.
This was an incredibly difficult issue to deal with, for two reasons:
The fact that CloudFront is mirroring our Rails app’s response headers requires you to twist your mind around. The CORS protocol is hard enough to understand as it is, but now you have to follow it at two levels: between the browser and CloudFront (when our Rails app uses it as a CDN), and between the browser and our Rails app (when some malicious site wants to abuse us).
CORS is really about a dialog between the browser and the 3rd-party resources a web page wants to access. (In our use-case, that’s the CloudFront CDN, serving assets for our app.) But since CloudFront gets its Access-Control response headers from our app, our app needs to serve those headers as if it is CloudFront talking, and simultaneously not grant permissions that would expose itself to the type of abuse that led to the Same-Origin Policy / CORS being developed in the first place. In particular, we should not grant * access to * resources on our site.
I found so much outdated information out there -- an endless line of blog posts and SO threads. CloudFront has improved its CORS support significantly since many of those posts, although it is still not perfect. (CORS should really be handled out-of-the-box.) And the gems themselves have evolved.
My setup: Rails 4.1.15 running on Heroku, with assets served from CloudFront. My app responds to both http and https, on both "www." and the zone apex, without doing any redirection.
I looked briefly at the font_assets gem mentioned in the question, but quickly dropped it in favor of rack-cors, which seemed more on point. I did not want to simply open up all origins and all paths, as that would defeat the point of CORS and the security of the Same-Origin Policy, so I needed to be able to specify the few origins I would allow. Finally, I personally favor configuring Rails via individual config/initializers/*.rb files rather than editing the standard config files (like config.ru or config/application.rb) Putting all that together, here is my solution, which I believe is the best available, as of 2016-04-16:
Gemfile
gem "rack-cors"
The rack-cors gem implements the CORS protocol in a Rack middleware.
In addition to setting Access-Control-Allow-Origin and related headers on approved origins, it adds a Vary: Origin response header, directing CloudFront to cache the responses (including the response headers) for each origin separately. This is crucial when our site is accessible via multiple origins (e.g. via both http and https, and via both "www." and the bare domain)
config/initializers/rack-cors.rb
## Configure Rack CORS Middleware, so that CloudFront can serve our assets.
## See https://github.com/cyu/rack-cors
if defined? Rack::Cors
Rails.configuration.middleware.insert_before 0, Rack::Cors do
allow do
origins %w[
https://example.com
http://example.com
https://www.example.com
http://www.example.com
https://example-staging.herokuapp.com
http://example-staging.herokuapp.com
]
resource '/assets/*'
end
end
end
This tells the browser that it may access resources on our Rails app (and by extension, on CloudFront, since it is mirroring us) only on behalf of our Rails app (and not on behalf of malicious-site.com) and only for /assets/ urls (and not for our controllers). In other words, allow CloudFront to serve assets but don't open the door any more than we have to.
Notes:
I tried inserting this after rack-timeout instead of at the head of the middleware chain.
It worked on dev but was not kicking in on Heroku, despite
having the same middleware (other than Honeybadger).
The origins list could also be done as Regexps.
Be careful to anchor patterns at the end-of-string.
origins [
/\Ahttps?:\/\/(www\.)?example\.com\z/,
/\Ahttps?:\/\/example-staging\.herokuapp\.com\z/
]
but I think it’s easier just to read literal strings.
Configure CloudFront to pass the browser's Origin request header on to our Rails app.
Strangely, it appears that CloudFront forwards the Origin header from the browser to our Rails app regardless whether we add it here, but that CloudFront honors our app’s Vary: Origin caching directive only if Origin is explicitly added to the headers whitelist (as of April 2016).
The request header whitelist is kind of buried.
If the distribution already exists, you can find it at:
https://console.aws.amazon.com/cloudfront/home#distributions
select the distribution
click Distribution Settings
go to the Behaviors tab
select the behavior (there will probably be only one)
Click Edit
Forward Headers: Whitelist
Whitelist Headers: Select Origin and click Add >>
If you have not created the distribution yet, create it at:
https://console.aws.amazon.com/cloudfront/home#distributions
Click Create Distribution
(For the sake of completeness and reproducibility, I'm listing all the settings I changed from the defaults, however the Whitelist settings are the only ones that are relevant to this discussion)
Delivery Method: Web (not RTMP)
Origin Settings
Origin Domain Name: example.com
Origin SSL Protocols: TLSv1.2 ONLY
Origin Protocol Policy: HTTPS only
Default Cache Behavior Settings
Viewer Protocol Policy: Redirect HTTP to HTTPS
Forward Headers: Whitelist
Whitelist Headers: Select Origin and click Add >>
Compress Objects Automatically: Yes
After changing all these things, remember that it can take some time for any old, cached values to expire from CloudFront. You can explicitly invalidate cached assets by going to the CloudFront distribution's Invalidations tab and creating an invalidation for *.
If you run Rails on Passenger and Heroku: (if not, jump straight to Noach Magedman's answer)
Noach Magedman's answer was very useful for me to set up CloudFront properly.
I also installed rack-cors exactly as described and whilst it worked fine in development, the CURL commands in production never returned any of the CORS configurations:
curl -H "Origin: https://tidyme-staging.com.au" -I http://tidyme-staging.com.au/assets/31907B_4_0-588bd4e720d4008295dcfb85ef36b233ee0817d7fe23c76a3a543ebba8e7c85a.ttf
HTTP/1.1 200 OK
Connection: keep-alive
Server: nginx/1.10.0
Date: Wed, 03 Aug 2016 00:29:37 GMT
Content-Type: application/x-font-ttf
Content-Length: 316664
Last-Modified: Fri, 22 Jul 2016 03:31:57 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Cache-Control: public
Accept-Ranges: bytes
Via: 1.1 vegur
Note that I ping the server directly without going through the CDN, the CDN then after invalidating all content should just forward whatever the server responds. The important line here is Server: nginx/1.10.0, which indicates that assets are served by nginx and not Rails. As a consequence, the rack-cors configurations do not apply.
The solution that worked for us is here: http://monksealsoftware.com/ruby-on-rails-cors-heroku-passenger-5-0-28/
It basically involved cloning and modifying the nginx config file for Passenger, which is not ideal since this copy needs to be maintained every time Passenger gets upgraded and the template changes.
===
Here's a summary:
Navigate to the root folder of your Rails project and make a copy of the nginx config template
cp $(passenger-config about resourcesdir)/templates/standalone/config.erb config/passenger_config.erb
Open config/passenger_config.erb and comment this line out
<%# include_passenger_internal_template('rails_asset_pipeline.erb', 8, false) %>
Add these configurations below the line mentioned above
### 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
location ~ "^/assets/.+\.(woff|eot|svg|ttf|otf).*" {
error_page 490 = #static_asset_fonts;
error_page 491 = #dynamic_request;
recursive_error_pages on;
if (-f $request_filename) {
return 490;
}
if (!-f $request_filename) {
return 491;
}
}
# 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 "";
}
location #static_asset_fonts {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Max-Age' 3628800;
}
location #dynamic_request {
passenger_enabled on;
}
### END your own configuration options ###
Change the Procfile to include this custom config file
web: bundle exec passenger start -p $PORT --max-pool-size 2 --nginx-config-template ./config/passenger_config.erb
Then deploy...
===
If you know of a better solution, please put in the comments.
After implementing, the CURL command yielded the following response:
curl -H "Origin: https://tidyme-staging.com.au" -I http://tidyme-staging.com.au/assets/31907B_4_0-588bd4e720d4008295dcfb85ef36b233ee0817d7fe23c76a3a543ebba8e7c85a.ttf
HTTP/1.1 200 OK
Connection: keep-alive
Server: nginx/1.10.0
Date: Wed, 03 Aug 2016 01:43:48 GMT
Content-Type: application/x-font-ttf
Content-Length: 316664
Last-Modified: Fri, 22 Jul 2016 03:31:57 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Cache-Control: public
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 3628800
Accept-Ranges: bytes
Via: 1.1 vegur
As of version 5.0, Rails allows for setting custom HTTP Headers for assets and you don't have to use the rack-cors or font-assets gems. In order to set Access-Control-Allow-Origin for assets (including fonts), just add the following code to config/environments/production.rb:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => '*'
}
The header value could also be a specific domain, like the following:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => 'https://www.example.org'
}
This worked for my app and I didn't need to change any settings on Cloudfront.
I just had the same issue and managed to solve it.
You've correctly told Cloudfront to allow those headers, but you haven't added those headers to where Cloudfront gets the font. Yes, your origin headers are allowed, but Heroku isn't sending those headers with the font anyway.
To fix this, you'll need to get the proper CORS headers added to the font on Heroku. Luckily, this is pretty easy.
First, add the rack/cors gem to your project. https://github.com/cyu/rack-cors
Next, configure your Rack server to load and configure CORS for any assets it serves. Add the following after your application preloads in config.ru
require 'rack/cors'
use Rack::Cors do
allow do
origins '*'
resource '/cors',
:headers => :any,
:methods => [:post],
:credentials => true,
:max_age => 0
resource '*',
:headers => :any,
:methods => [:get, :post, :delete, :put, :patch, :options, :head],
:max_age => 0
end
end
This sets any resources returned from Heroku to have the proper CORS headers applied. You can restrict the application of headers depending on your file and security needs.
Once deployed, go into Cloudfront and begin an invalidation on anything that was previously giving you a CORS permission error. Now when Cloudfront loads a fresh copy from Heroku, it will have the correct headers, and Cloudfront will pass those headers on to the client as previously configured with your Origin permissions.
To make sure you're serving the proper headers from your server, you can use the following curl command to validate your headers:
curl -I -s -X GET -H "Origin: www.yoursite.com" http://www.yoursite.dev:5000/assets/fonts/myfont.svg
You should see the following headers returned:
Access-Control-Allow-Origin: www.yoursite.com
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH, OPTIONS, HEAD
Access-Control-Max-Age: 0
Access-Control-Allow-Credentials: true
Here is a repo the demonstrates serving a custom font with Rails 5.2 that works on Heroku. It goes further and optimizes serving the fonts to be as fast as possible according to https://www.webpagetest.org/
https://github.com/nzoschke/edgecors
Asset Pipeline and SCSS
Place fonts in app/assets/fonts
Place the #font-face declaration in an scss file and use the font-url helper
From app/assets/stylesheets/welcome.scss:
#font-face {
font-family: 'Inconsolata';
src: font-url('Inconsolata-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
body {
font-family: "Inconsolata";
font-weight: bold;
}
Serve from CDN with CORS
I'm using CloudFront, added with the Heroku Edge addon.
If you're using your own CloudFront, make sure to configure it to forward the browser Origin header to your backend origin.
First configure a CDN prefix and default Cache-Control headers in production.rb:
Rails.application.configure do
# e.g. https://d1unsc88mkka3m.cloudfront.net
config.action_controller.asset_host = ENV["EDGE_URL"]
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=31536000'
}
end
If you try to access the font from the herokuapp.com URL to the CDN URL, you will get a CORS error in your browser:
Access to font at 'https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf' from origin 'https://edgecors.herokuapp.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource. edgecors.herokuapp.com/
GET https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf net::ERR_FAILED
So configure CORS to allow access to the font from Heroku to the CDN URL:
module EdgeCors
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2
config.middleware.insert_after ActionDispatch::Static, Rack::Deflater
config.middleware.insert_before 0, Rack::Cors do
allow do
origins %w[
http://edgecors.herokuapp.com
https://edgecors.herokuapp.com
]
resource "*", headers: :any, methods: [:get, :post, :options]
end
end
end
end
Serve gzip Font Asset
The asset pipeline builds a .ttf.gz file but doesn't serve it. This monkey patch changes the asset pipeline gzip whitelist to a blacklist:
require 'action_dispatch/middleware/static'
ActionDispatch::FileHandler.class_eval do
private
def gzip_file_path(path)
return false if ['image/png', 'image/jpeg', 'image/gif'].include? content_type(path)
gzip_path = "#{path}.gz"
if File.exist?(File.join(#root, ::Rack::Utils.unescape_path(gzip_path)))
gzip_path
else
false
end
end
end
The ultimate result is a custom font file in app/assets/fonts served from a long-lived CloudFront cache.
Probably best is to use rack-cors gem. In the spirit of do-it-yourself and a follow-up to #GeekJock's answer. If one doesn't want to use rack-cors gem, this is poor man's CORS headers handling for a situation where for example we care only about static fonts assets (e.g. replacing the font_assets obsoleted gem).
Like in the other answer, you put in app config:
config.public_file_server.headers = {
'Access-Control-Allow-Origin' => '*'
}
To handle OPTIONS pre-flight requests, you can write a route matcher somewhere under /lib:
module FontAssetsConstraint
FONT_EXTENSIONS = %w[eot svg ttf otf woff woff2].freeze
module_function
def matches?(request)
extension = request.params["format"]
extension.present? && FONT_EXTENSIONS.include?(extension)
end
end
And then add a route definition config/routes.rb to catch those to reply:
Rails.application.routes.draw do
# Respond to pre-flight CSRF requests for font assets
if Rails.configuration.public_file_server.enabled &&
Rails.configuration.public_file_server.headers.include?("Access-Control-Allow-Origin")
constraints FontAssetsConstraint do
match "*path", via: :options, to: ->(hash) { [204, Rails.configuration.public_file_server.headers, []] }
end
end
Alternatively to writing a route matcher and a definition you can create your own middleware to catch fonts:
class AssetsOptionsResponder
TYPES = %w(eot svg ttf otf woff woff2).freeze
def initialize(app)
#app = app
end
def call(env)
if env["REQUEST_METHOD"] == "OPTIONS" && targeted?(env["PATH_INFO"])
[204, access_control_headers, []]
else
#app.call(env)
end
end
private
def targeted?(pathinfo)
return if pathinfo.blank?
TYPES.include? extension(pathinfo)
end
def extension(pathinfo)
pathinfo.split("?").first.split(".").last
end
def access_control_headers
Rails.configuration.public_file_server.headers
end
end
Then in app config or an initializer, you can add this middleware:
Rails.application.configure do
if defined?(ActionDispatch::Static) &&
Rails.configuration.public_file_server.enabled &&
Rails.configuration.public_file_server.headers.include?("Access-Control-Allow-Origin")
config.middleware.insert_before ActionDispatch::Static, AssetsOptionsResponder
end
end
# Gemfile
gem 'rack-cors'
# config/initializers/cors.rb
## very permissive
origins '*'
resource '*', headers: :any, methods: [:get]
## example of specifying only what's necessary
origins 'app.example.com'
resource '/packs/*', headers: :any, methods: [:get] # webpack
resource '/assets/*', headers: :any, methods: [:get] # asset pipeline

Rails 4 + Nginx - serve font via CloudFront won't load cause by CORS

I've website at http://revoniaga.com which is run RoR but the font won't load where it said "blocked from loading by Cross-Origin Resource Sharing policy". It cause by Amazon CloudFront. I've try everything but still same result.
First, I put my font assets under /app/assets/font
then in my css (vendor/assets/stylesheets/font-awesome.css), I use something like this:
#font-face {
font-family: 'FontAwesome';
src: url('/assets/fontawesome-webfont.eot?v=4.3.0');
//and so on for other font format
}
At my /etc/nginx/sites-available/revoniaga_production, I put
location ~* \.(eot|otf|svg|ttf|woff|woff2)$ {
add_header Access-Control-Allow-Origin *;
}
Then when I curl:
curl -I https://d3bkb7gt2ds4m6.cloudfront.net/assets/fontawesome-webfont.woff2
It said
HTTP/1.1 404 Not Found
Content-Type: text/html
Content-Length: 168
Connection: keep-alive
Server: nginx/1.6.2
Date: Wed, 18 Mar 2015 09:34:29 GMT
Age: 242
X-Cache: Error from cloudfront
Via: 1.1 404e3b476748051f3f9bc690b72173b4.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 3yUNjkFQHC83FlbEwVMBSddpQND_4GbXauyeTtDyKawS6GzftUwXRA==
I'm also restarting my server "service nginx restart" but still nothing happen
Here is my CloudFront which is the default setting
Everything work fine in my dev env
I couldn't figure why this happening. Please help
Thanks in advance
Problem solved by clean install the server, upgrade ruby to 2.2.1 plus.. looks like the issue come from the server itself. my setting before was fine..

EmberJS calls to an MVC Web API result in a CORS message when authorization fails

I am using EmberJS to communicate with an MVC Web API using Auth0 for authorization. When I have a valid bearer token I can communicate and retrieve data from the Web API just fine. When the token expires the Web API returns an an expected 401 the following message is displayed in the browser console:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200'
The ajaxError method of the RESTAdapter is called as expected, but the jqXHR.status field is 0.
export default
DS.RESTAdapter.extend({
namespace: 'api',
host: webApiUrl,
headers: function() {
return {
"Authorization": 'Bearer ' + localStorage.getItem('userToken'),
};
}.property().volatile(),
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 401) {
Ember.Logger.info("Not Authorized");
}
Ember.Logger.info("Error " + jqXHR.status + " calling API redirecting to login.");
}
});
Here is a sample of the response returned from the API:
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcU291cmNlXFBheWNvckRldlxTb3VyY2VcSW50ZWdyYXRpb25cR2VuZXJhbFxNYWluXFBheWNvci5JbnRlZ3JhdGlvbi5PcGVyYXRpb25zXFBheWNvci5JbnRlZ3JhdGlvbi5PcGVyYXRpb25zLkFwaVxhcGlcbG9nZ2luZ0V2ZW50cw==?=
X-Powered-By: ASP.NET
Date: Fri, 30 Jan 2015 16:45:35 GMT
Content-Length: 927
I have tried XML and plan/text Content-types, but the result is the same.
I don't believe this is an actual CORS issue because this problem only occurs when the API returns an error; otherwise I'm downloading and displaying the data just fine.
Does anyone know what the issue might be?
Thanks in advance.
I was having the same issue and it was a CORS issue.
I'm not sure what backend your api server is but mine is a Rails API and the solution was to move the CORS middleware to the top of the middleware stack
config.middleware.insert_before 0, 'Rack::Cors' do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
The issue is a bit confusing because before fixing the problem if I made a request using cURL I receive the correct response with the right headers etc
$ curl -I -X GET -H 'Accept: application/json' http://api.example.dev/foo
HTTP/1.1 401 Unauthorized
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Pacu-Media-Type: v1
Content-Type: application/json; charset=utf-8
X-Rack-CORS: preflight-hit; no-origin
X-Request-Id: def30b49-895f-4581-82ec-87bcfb6c44e5
X-Runtime: 0.010945
Date: Sun, 22 Feb 2015 03:30:35 GMT
Connection: close
and the correct error message
$ curl -X GET -H 'Accept: application/json' http://api.example.dev/foo
{"errors":[{"id":"01ac93be-ea7a-4d8e-b86b-9ea1f4136b11","title":"unauthorized","detail":"The access token is invalid","status":"401"}
The problem is that cURL is not making a cross site request and therefore CORS isn't needed. When attempting to connect using jQuery the request would fail with the following error in the browser's dev console.
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access. The response had HTTP status code 401.
The response status was 401 but the response body was empty and the jqXHR.status was set to 0.
Again the reason was because my Rails backend CORS middleware needed to be at the top of the rack stack order. In Rails since I am using the Routes Engine as an exceptions app config.exceptions_app = self.routes I needed the CORS middleware to load before it in ActionDispatch::ShowExceptions

Resources