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

CssFirefoxAmazon S3Font FaceCors

Css Problem Overview


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:

https://stackoverflow.com/questions/2856502/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.

Css Solutions


Solution 1 - Css

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 - Css

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)

  1. 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 - Css

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 - Css

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...

Solution 5 - Css

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 6 - Css

In my case, I hadn't defined XML namespace and version in CORS configuration. Defining those worked.

Changed

<CORSConfiguration>

to

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">

Solution 7 - Css

There is a better and easier way!

I personally prefer using my DNS subdomains to solve this problem. If my CDN is behind cdn.myawesomeapp.com instead of sdf73n7ssa.cloudfront.net then browsers are not going to freakout and block them as cross domain security problems.

To point your subdomain to your AWS Cloudfront domain go to AWS Cloudfront control panel, select your Cloudfront distribution and enter your CDN subdomain into the Alternate Domain Names (CNAMEs) field. Something like cdn.myawesomeapp.com will do.

Now you can go to your DNS provider (like AWS Route 53) and create a CNAME for cdn.myawesomeapp.com pointing to sdf73n7ssa.cloudfront.net.

http://blog.cloud66.com/cross-origin-resource-sharing-cors-blocked-for-cloudfront-in-rails/

Solution 8 - Css

This configuration worked for me. I can list object, retrieve, update and delete.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>http://localhost:3000</AllowedOrigin>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
    <ExposeHeader>ETag</ExposeHeader>
    <ExposeHeader>x-amz-meta-custom-header</ExposeHeader>
  </CORSRule>
</CORSConfiguration>

Solution 9 - Css

2021 Solution, without risking security by allowing "*" in AllowedDomains.

Step 1) Allow S3 to take in CORS

In S3 bucket > Permissions > Cross-origin resource sharing (CORS), add the list of your domain/domains in AllowedOrigins. See the official doc for examples. You only need GET for AllowedMethods.

Step 2) Tell CloudFront to send over the CORS headers

In your CloudFront Behavior < Origin Request Policy, make sure you select a policy that sends over origin and access-control-request-headers headers, e.g. Managed-CORS-S3Origin.

enter image description here

Step 3) [Optional, only if you have more than one domain]

See this answer of mine on how to handle multiple domains in CORS for S3+CloudFront.

Step 4) Invalidate Your CloudFront Distribution

Good luck!

Solution 10 - Css

<ifModule mod_headers.c>

   Header set Access-Control-Allow-Origin: http://domainurl.com

</ifModule>

Simple Solution

Solution 11 - Css

This is not related to fonts but to images, it might be an edge case, but as it happened to me, it might happen to another one. I'll leave this here hoping it will help someone:

If you are in the scenario "I have done everything they told, but it still won't work" probabily it's a cache related problem in Chrome and Safari. Let's suppose your server has a proper CORS configuration set:

<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
    </CORSRule>
</CORSConfiguration>

and in Firefox everything works fine, but in Chrome and Safari it doesn't. If you are accessing to your remote image path from both a simple <img src="http://my.remote.server.com/images/cat.png"> tag and from a js Image element src, like in the following way:

var myImg = new Image()
myImg.crossOrigin = 'Anonymous'
myImg.onload = () => {
  // do stuff (maybe draw the downloaded img on a canvas)
}
myImg.src = 'http://my.remote.server.com/images/cat.png'

You might obtain the No 'Access-Control-Allow-Origin' error in Chrome and Safari. This happen because the first <img> somehow messes up the browser cache, and when you are trying to access the same image later (on the in-code Image element), it simply break. To avoid this, you can add a fictitious GET param to one .src path, in order to force the browser to re-request the image and avoid to use cache, like this:

<img src="http://my.remote.server.com/images/cat.png?nocache=true"></img>

Solution 12 - Css

Restarting my spring boot application (server) solved the problem for me.

I had configured CORS correctly on S3. The curl was giving the correct response with origin header. Safari was fetching the font correctly. It was only the chrome who was not willing to accept the CORS.

Not sure what exactly caused the behaviour. Must be something to do with If-modified-since

Solution 13 - Css

Yes, of course. Firefox supports CORS for fonts, just like the spec requires at http://dev.w3.org/csswg/css3-fonts/#allowing-cross-origin-font-loading

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVKenView Question on Stackoverflow
Solution 1 - CssVKenView Answer on Stackoverflow
Solution 2 - CssEamonn GahanView Answer on Stackoverflow
Solution 3 - Cssluigi7upView Answer on Stackoverflow
Solution 4 - CssnzifnabView Answer on Stackoverflow
Solution 5 - CsseducoutinhoView Answer on Stackoverflow
Solution 6 - CssGaurav ToshniwalView Answer on Stackoverflow
Solution 7 - CssmsrootView Answer on Stackoverflow
Solution 8 - CssShahidView Answer on Stackoverflow
Solution 9 - CssAidinView Answer on Stackoverflow
Solution 10 - CssO-mkarView Answer on Stackoverflow
Solution 11 - CssNicola EliaView Answer on Stackoverflow
Solution 12 - CssSujit KamtheView Answer on Stackoverflow
Solution 13 - CssBoris ZbarskyView Answer on Stackoverflow