Amazon S3 CORS (Cross-Origin Resource Sharing) and Firefox cross-domain font loading

95,359

Solution 1

Update September 10, 2014:

You shouldn't need to do any of the query string hacks below anymore since Cloudfront properly supports CORS now. See http://aws.amazon.com/blogs/aws/enhanced-cloudfront-customization/ and this answer for more info: https://stackoverflow.com/a/25305915/308315


OK, I finally got the fonts working using the config below with a little tweak from examples in the documentation.

My fonts are hosted on S3, but fronted by cloudfront.

I'm not sure why it works, my guess is probably that the <AllowedMethod> GET and <AllowedHeader> Content-* is needed.

If anyone proficient with Amazon S3 CORS config can shed some lights on this, it'll be greatly appreciated.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>https://mydomain.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Content-*</AllowedHeader>
        <AllowedHeader>Host</AllowedHeader>
    </CORSRule>
    <CORSRule>
        <AllowedOrigin>https://*.mydomain.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Content-*</AllowedHeader>
        <AllowedHeader>Host</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

edit:

Some developers are facing issues of Cloudfront caching the Access-Control-Allow-Origin header. This issue has been addressed by the AWS staff in the link (https://forums.aws.amazon.com/thread.jspa?threadID=114646) below, commented by @Jeff-Atwood.

From the linked thread, it is advised, as a workaround, to use a Query String for differentiating between calls from different domains. I'll reproduce the shortened example here.

Using curl to check response headers:

Domain A: a.domain.com

curl -i -H "Origin: https://a.domain.com" http://hashhashhash.cloudfront.net/font.woff?https_a.domain.com

Response headers from Domain A:

Access-Control-Allow-Origin: https://a.domain.com
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
X-Cache: Miss from Cloudfront

Domain B: b.domain.com

curl -i -H "Origin: http://b.domain.com" http://hashhashhash.cloudfront.net/font.woff?http_b.domain.com

Response headers from Domain B:

Access-Control-Allow-Origin: http://b.domain.com
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
X-Cache: Miss from Cloudfront

You will notice the Access-Control-Allow-Origin has returned different values, which got past the Cloudfront caching.

Solution 2

After some tweaking I seem to have got this to work without the query string hack. More info here: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorS3Origin.html#RequestS3-cors

I'm going to go through my entire setup so that it's easy to see what I've done, hopefully this helps others.

Background Information: I'm using a Rails app that has the asset_sync gem to put assets onto S3. This includes fonts.

Within S3 console, I clicked on my bucket, properties and 'edit cors configuration', here: CORS config button

Inside the textarea I have something like:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>https://*.example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Then within Cloudfront panel (https://console.aws.amazon.com/cloudfront/home) I created a distribution, added an Origin that pointed to my S3 bucket adding an origin

Then added a behavior for a default path to point to the S3 based origin I setup. What I also did was click on Whitelist headers and added Origin: adding a behavior and whitelist headers

What happens now is the following, which I believe is right:

1) Check that S3 headers are being set correctly

curl -i -H "Origin: https://example.com" https://s3.amazonaws.com/xxxxxxxxx/assets/fonts/my-cool-font.ttf
HTTP/1.1 200 OK
x-amz-id-2: Ay63Qb5uR98ag47SRJ91+YALtc4onRu1JUJgMTU98Es/pzQ3ckmuWhzzbTgDTCt+
x-amz-request-id: F1FFE275C0FBE500
Date: Thu, 14 Aug 2014 09:39:40 GMT
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Cache-Control: public, must-revalidate, proxy-revalidate, max-age=180
Last-Modified: Mon, 09 Dec 2013 14:29:04 GMT
ETag: "98918ee7f339c7534c34b9f5a448c3e2"
Accept-Ranges: bytes
Content-Type: application/x-font-ttf
Content-Length: 12156
Server: AmazonS3

2) Check Cloudfront works with the headers

curl -i -H "Origin: https://example.com" https://xxxxx.cloudfront.net/assets/fonts/my-cool-font.ttf
HTTP/1.1 200 OK
Content-Type: application/x-font-ttf
Content-Length: 12156
Connection: keep-alive
Date: Thu, 14 Aug 2014 09:35:26 GMT
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
Cache-Control: public, must-revalidate, proxy-revalidate, max-age=180
Last-Modified: Mon, 09 Dec 2013 14:29:04 GMT
ETag: "98918ee7f339c7534c34b9f5a448c3e2"
Accept-Ranges: bytes
Server: AmazonS3
Vary: Origin
X-Cache: Miss from cloudfront
Via: 1.1 77bdacfea247b6cbe84dffa61da5a554.cloudfront.net (CloudFront)
X-Amz-Cf-Id: cmCxaUcFf3bT48zpPw0Q-vDDza0nZoWm9-_3qY5pJBhj64iTpkgMlg==

(Note the above was a miss from cloudfront because these files are cached for 180 seconds, but the same was working on hits)

3) Hit cloudfront with a different origin (but one that is allowed on CORS for the S3 bucket) - the Access-Control-Allow-Origin is not cached! yay!

curl -i -H "Origin: https://www2.example.com" https://xxxxx.cloudfront.net/assets/fonts/my-cool-font.ttf
HTTP/1.1 200 OK
Content-Type: application/x-font-ttf
Content-Length: 12156
Connection: keep-alive
Date: Thu, 14 Aug 2014 10:02:33 GMT
Access-Control-Allow-Origin: https://www2.example.com
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
Cache-Control: public, must-revalidate, proxy-revalidate, max-age=180
Last-Modified: Mon, 09 Dec 2013 14:29:04 GMT
ETag: "98918ee7f339c7534c34b9f5a448c3e2"
Accept-Ranges: bytes
Server: AmazonS3
Vary: Origin
X-Cache: Miss from cloudfront
Via: 1.1 ba7014bad8e9bf2ed075d09443dcc4f1.cloudfront.net (CloudFront)
X-Amz-Cf-Id: vy-UccJ094cjdbdT0tcKuil22XYwWdIECdBZ_5hqoTjr0tNH80NQPg==

Note above that the domain has successfully changed without a query string hack.

When I change the Origin header, there seems to always be a X-Cache: Miss from cloudfront on the first request then afterwards I get the expected X-Cache: Hit from cloudfront

P.S. It is worth noting that when doing curl -I (capital I) will NOT show the Access-Control-Allow-Origin headers as it only a HEAD, I do -i to make it a GET and scroll up.

Solution 3

My fonts were served correctly until the last push to Heroku... I don't know why, but the wildcard in the CORS allowed origin stopped working. I added all of my prepro and pro domains to the CORS policy in the bucket setting so now it looks like this:

<CORSConfiguration>
    <CORSRule>
        <AllowedOrigin>http://prepro.examle.com</AllowedOrigin>
        <AllowedOrigin>https://prepro.examle.com</AllowedOrigin>
        <AllowedOrigin>http://examle.com</AllowedOrigin>
        <AllowedOrigin>https://examle.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>

</CORSConfiguration>

UPDATE: add your http://localhost:PORT too

Solution 4

In Amazon S3 CORS configuration (S3 Bucket / Permissions / CORS) if you use this:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>

CORS works well for Javascript and CSS files, but It does not work for Font files.

You have to specify the domain to allow CORS using the pattern expressed in the @VKen answer: https://stackoverflow.com/a/25305915/618464

So, use this:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>https://*.mydomain.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Remember to replace "mydomain.com" for your domain.

After this, invalidate the CloudFront cache (CloudFront / Invalidations / Create Invalidation) and It will work.

Solution 5

Well, the documentation states that you can stick the configuration as "the cors subresource in your bucket." I took this to mean I would create a file called "cors" at the root of my bucket with the configuration, but this would not work. In the end I had to login to the Amazon S3 administration area and add the configuration within the properties dialog of my bucket.

S3 could use some better documentation...

Share:
95,359
VKen
Author by

VKen

More than 10 years in software engineering, mostly programs with Javascript, Python, and Php. Dabbled a little in C, C++, Qbasic, Visual Basic, Java, Ruby, Erlang, Lua, ActionScript3, Scheme (LISP), (Bash/Zsh) shell scripts, ELM, and Go (Golang). A VIM user, who also dabbled in the dark side by exploring CLISP in EMACS. Does web development with HTML5 and CSS3, and JavaScript (ES6/2015+) &amp; server-side JS (node.js). Frameworks, oh frameworks, more and more of them everyday. For frontend UI in web browser, there are javascript and compiled-to-javascript languages. Plays with Node.js (io.js a short while), jQuery, Backbone, Angular 1.x, grunt, yeoman, kendoUI, dojokit, and toying with ELM (identity-crisis) and React + Redux (identity-crisis). Explores the javascript/node.js world with NPM. Takes care of the dependency mess with RequireJS, and Webpack. For Python (CPython), plays with Django, Scrapy, Pandas, NLTK. Explores the Python world with Python-pip and the awesome batteries-included libraries/packages in PyPI and various git repository platforms. For Php, plays with Laravel, CodeIgniter. Rarely touches Symphony, but more of its components. Explores the Php world with Composer. DevOps is cool. Automation with python-fabric, Ansible, puppet. Containerization with Openshift, Kubernetes, Docker, Helm. Big data scaling with dataflow design. Exploring data-intensive application design with Apache Kafka and RabbitMQ. Exploring the world of artificial intelligence via machine learning.

Updated on May 20, 2021

Comments

  • VKen
    VKen almost 3 years

    There has been a long standing issue with Firefox not loading font from different origin than the current webpage. Usually, the issue arise when the fonts are served on CDNs.

    Various solutions has been raised in other questions:

    CSS @font-face not working with Firefox, but working with Chrome and IE

    With the introduction of Amazon S3 CORS, is there a solution using CORS to address the font loading issue in Firefox?

    edit: It would be great to see a sample of the S3 CORS configuration.

    edit2: I have found a working solution without actually understanding what it did. If anyone could provide more detailed explanations about the configs and the background magic that happens on Amazon's interpretation of the config, it will be greatly appreciated, as with nzifnab who put up a bounty for it.

  • VKen
    VKen over 11 years
    Thanks for your prompt response, Boris Zbarsky. Would you be able to show some example configurations for the S3 CORS settings?
  • Boris Zbarsky
    Boris Zbarsky over 11 years
    I've never looked into configuring S3... As far as what to send on the HTTP level, if you're OK with it just sending "Access-Control-Allow-Origin: *" in the HTTP response for the font files should work.
  • VKen
    VKen over 11 years
    Thanks, I'm trying to find out exactly how to do that setting with the S3 CORS configurations.
  • Oleg
    Oleg over 11 years
    have you experienced issues similar to what's described here - the Access-Control-Allow-Origin header gets cached and invalidate CORS when a subsequent request is made through a different subdomain?
  • VKen
    VKen over 11 years
    Yep, but I was lucky to spot some new interface changes at the properties panel. I've been editing bucket policies, so naturally I hunt for CORS configuration in the same panel.
  • VKen
    VKen over 11 years
    @o.v. I do not experience the issue as I explicitly set the domains that uses the resources. I've read the link you posted before. I vaguely remembered some replies on another thread saying that domains have to be explicitly stated, so <AllowedOrigin>*</AllowedOrigin> is not actually allowed, due to some restrictions. I can't find those reply posts now, it could be blog post I read elsewhere. Hope that helps.
  • Ben Hull
    Ben Hull over 11 years
    You can have multiple AllowedOrigin elements inside a single CORSRule elements, so you could combine those CORSRules into a single element, since the other elements in them are identical.
  • Dan
    Dan over 11 years
    The OP states that Cloudfront is being used in front of S3, did you not have a problem with Cloudfront caching the Access-Control-Allow-Origin header and thus a response from one domain breaks the other?
  • Peter Burns
    Peter Burns over 11 years
    @dan if the S3 bucket is served by CloudFront, it looks like the answer is to vary the font querystring by domain as documented in this official Amazon answer: forums.aws.amazon.com/thread.jspa?threadID=114646
  • Zach Lipton
    Zach Lipton about 11 years
    This has been an extremely frustrating issue. The good news is that S3 now appears to be doing the right thing, so at least it is possible to serve everything other than webfonts through CloudFront and serve the font files directly from S3. Sadly, the querystring hack isn't really practical in our application without more significant refactoring, as the assets are all served through the Rails asset pipeline, and there's no convenient way to tweak the asset URLs at request time (they are all generated during deployment when the assets are precompiled). The font's URL in css is already up on S3.
  • VKen
    VKen about 11 years
    @ZachLipton I share your pain. I have problems with IE9 caching CSS file to use with both the WWW subdomain, and the base domain without the the www. This causes the frustrating font loading problem all over again.
  • Richlewis
    Richlewis almost 11 years
    worked for me, i was looking to set this in my application, who knew it would be so simple
  • Simon
    Simon almost 11 years
    @VKen Thanks for this! In case anyone else can't figure out why this doesn't appear to be working, I had to invalidate objects in CloudFront otherwise the cached versions were used: docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/…
  • philfreo
    philfreo almost 11 years
    The querystring solution may help, but it isn't a real solution. The only real solution will be if Cloudfront supports the Vary: Origin header. (Currently it only respects Vary: Accept-Encoding)
  • Jim Wrubel
    Jim Wrubel over 10 years
    I was able to get this working by explicitly setting the <AllowedOrigin/> headers. When we used a wildcard, the headers were not included even after invalidating the files on cloudfront. I did not need to use the querystring option.
  • Hitesh
    Hitesh about 10 years
    Hi @VKen , I am having some what same issue stackoverflow.com/q/22375472/1877909 but it is not working for me. I too added aws cors configuration
  • Noz
    Noz almost 10 years
    I believe you meant to use curl -I ?
  • khamaileon
    khamaileon almost 10 years
    Works for me too. My fonts are hosted on the bucket itself.
  • iwasrobbed
    iwasrobbed over 9 years
    Important note to the above: You shouldn't need to do any of the query string hacks anymore since Cloudfront properly supports CORS now. See aws.amazon.com/blogs/aws/enhanced-cloudfront-customization and this answer for more info: stackoverflow.com/a/25305915/308315
  • iwasrobbed
    iwasrobbed over 9 years
    Worked when all the others didn't. Thanks for taking the time to post in such detail!
  • Michael Gorham
    Michael Gorham over 9 years
    It works!! FYI - I had a huge http response text when testing this... gonna edit the answer to use this curl solution ... stackoverflow.com/questions/10060098/…
  • Eamonn Gahan
    Eamonn Gahan over 9 years
    Cool thanks guys - glad to see it's working for others.
  • nothing-special-here
    nothing-special-here over 9 years
    I can't tell you how much you have helped us! +1
  • Sébastien Saunier
    Sébastien Saunier about 9 years
    +1 for adding the customer header Origin from the viewers so that Cloudfront caches the object based on that header (and forward the server CORS headers back to the user)
  • kishore
    kishore about 9 years
    thanks it worked for me. @EamonnGahan I have a doubt why is it working fine for images and css files i.e other static content.
  • Tamik Soziev
    Tamik Soziev about 9 years
    I recommend using Advanced Rest Client Chrome Extension ( chrome.google.com/webstore/detail/advanced-rest-client/… ) in order to see what is going on with the headers, it does a great job of separating content from headers in the output so you dont have to scroll as much as you do in terminal
  • Shahid
    Shahid almost 9 years
    you need to change domain, as i was testing from localhost, Just look at this page for CORS: docs.aws.amazon.com/AWSJavaScriptSDK/guide/…
  • webStuff
    webStuff over 8 years
    As simple as that! Just white list the 'Origin' header. Thanks Eamonn.
  • kielni
    kielni over 8 years
    Thanks so much for the comment about using -i vs -I when testing with curl. I couldn't figure out what was wrong with my CORS config, and the answer was nothing, my curl request just wasn't showing the header.
  • maletor
    maletor about 8 years
    This breaks SSL or rather it costs a lot of money to do with SSL therefore a lot of people don't do this.
  • rmontgomery429
    rmontgomery429 about 8 years
    Thank you for sharing this solution. This worked for me.
  • keepthepeach
    keepthepeach over 7 years
    AWS just adds a similar CORS configuration in the popup by default. I lost few minutes before realizing that it's kind of a placeholder, you should save the configuration before it's taken in account for real!
  • Cerin
    Cerin almost 7 years
    This doesn't work. I added that CORS configuration to my existing bucket, and Chrome still blocks all requests to Cloudfront.
  • CoatedMoose
    CoatedMoose almost 7 years
    Why the default template doesn't automatically include this is beyond me.
  • Vinay Vissh
    Vinay Vissh about 6 years
    Thanks for sharing! Gave me the idea to just add this header as 'meta-data' while uploading static assets to cloud storage. (Though that way it will work with only 1 particular domain or all domains)
  • Gustavo Contreiras
    Gustavo Contreiras about 2 years
    Also AWS console is not accepting XML in CORS configuration of the S3 Bucket, just JSON.