cachingnginxfontsamazon-cloudfrontcross-domain-policy

Issue loading webfonts when served from AWS Cloudfront


So, here's my conundrum! I have been trying to get Cloudfront to play nicely with my nginx server for the past three days...have read countless StackOverflow posts and blog articles...scoured the interwebs and I am still stuck with issues surrounding cross-domain access policies when it comes to Cloudfront serving fonts. I am going to post my complete setup in hopes that someone with more expertise may help me figure out what is going on. In the future, I hope this post will serve many others facing similar issues. Here goes...

Nginx config:

I have a nginx webserver with the following server block configuration. (...truncated for brevity)

    server {
        server_name  example.com www.example.com;
        root /var/www/example.com/html;
        index index.html index.htm;

        location / {
            try_files $uri $uri/ =404;
        }
        location /assets {
            autoindex on;
        }

        # Media
        location ~* \.(jpe?g|gif|png|ico|cur|gz|svgz?|mp4|ogg|ogv|webm|htc|webp)$ {
            expires 1M;
            access_log off;
            add_header Cache-Control public;
        }

        # Fonts
        location ~* \.(eot|ttf|woff|woff2|svg)$ {
            expires 365d;
            access_log off;
            add_header Cache-Control public;
            add_header Access-Control-Allow-Origin example.com;
            // Have also tried setting the "Access-Control-Allow-Origin" header to "*", but I'd prefer not to do this for security reasons.
        }

    }

FYI: All of my website files I would like to serve and offload to CloudFront are in the /assets directory of my virtual host. (ie. http://example.com/assets/..)


CloudFront:

I have created a new CloudFront distribution with the following settings:

Note: I am not using S3 to host my website files and assets.

General:

Origins:

Behaviors:


DNS settings:

A portion of my Zone file...

example.com. 1800 IN A 12.34.567.890  //faked IP here for privacy reasons
www.example.com. 1800 IN CNAME example.com.
static.example.com. 1800 IN CNAME kg72kgf83nhfy3.cloudfront.net.  //faked CloudFront dist. domain name here for privacy reasons

What's happening? Why it no- worky?

So, CloudFront does its thing processing and deploying my distribution, and I'm assuming pulling the assets from my '/assets/..' web directory. All src, href, and CSS url() references point to http://static.example.com in my current HTML and CSS documents, including all font-face references. After the distribution is deployed, I hit my site http://example.com in a browser.

It appears that all static site assets are served correctly from CloudFront w/ appropriate caching headers as defined in my nginx config...EXCEPT...webfonts. I am getting missing fonts on the page and cross-domain access policy error messages in my browser console.

Headers—

Loading my website at http://example.com, everything seems to work (ie. images) EXCEPT the webfonts. Checking the browser console outputs the following message for every font:

Font from origin 'http://static.example.com' has been blocked from loading by Cross-Origin Resource Sharing policy: The 'Access-Control-Allow-Origin' header contains the invalid value 'example.com'. Origin 'http://example.com' is therefore not allowed access.

So, anyone have any thoughts?? I would greatly appreciate the help/input. I'm a young web developer just trying to learn.

Thank you! :)
–Kyle



Footnotes:


Solution

  • The explanation is here:

    The 'Access-Control-Allow-Origin' header contains the invalid value 'example.com'. Origin 'http://example.com' is therefore not allowed access.

    The origin is http://example.com... not example.com. Your response header has the wrong value. An origin is, by definition, scheme + hostname + port (with the implicit ports 80 and 443 omitted for http and https on standard ports).

    When the request isn't subject to cross-origin rules, such as is the case with images, this misconfiguration on your web server is being ignored by the browser. For fonts, you're hitting this wall because the value is malformed.

    add_header Access-Control-Allow-Origin example.com;

    This is the root of your problem. You need to respond with the same origin sent by the browser in the Origin: header, assuming that origin is indeed valid and should be allowed. I'm not an nginx specialist, by according to this answer, you can validate the incoming origin with a regex, and set the response accordingly:

    if ($http_origin ~* "^https?://.*example\.com$" ) {
        add_header Access-Control-Allow-Origin $http_origin;
    }
    

    Not being familiar with the quirks of nginx regexes, mine is a little permissive, but you get the idea.

    Before this will work, we need to fix the CloudFront cache behavior.

    Forward Headers: Whitelist

    Access-Control-Allow-Origin

    That isn't a request header... that's a response header, so whitelisting it doesn't actually do anything.

    You will, instead, need to whitelist the Origin header so that it will be forwarded to the web server by CloudFront, so that the server can respond with that same value in Access-Control-Allow-Origin:, as illustrated above.

    Access-Control-Request-Headers and Access-Control-Request-Method should probably also be whitelisted, though for GET requests, I don't know that they will matter.

    You also should probably modify the allowed methods to include OPTIONS in addition to GET and HEAD.


    Bonus material:

    My plan is to append hashed query strings to the end of every file (ie. //static.example.com/assets/images/image.png?622c6911) to invalidate the CloudFront cache.

    In that case, you need Forward Query Strings set to Yes.

    CloudFront caches objects based on what it actually sends to the server. Appending a query string will only invalidate the browser cache, not the CloudFront cache, if query strings are not forwarded to the origin. Yes, you need this enabled even if your server doesn't want or need the query strings, if the query strings are intended to be used for cache-busting CloudFront. That's why not forwarding them "improves caching." It will be "improved" by CloudFront ignoring them entirely when determining whether it already has a cached version of an object.

    Also, note that the Via header doesn't contain sensitive information, neither does X-Amz-Cf-Id.