Wednesday 5 November 2014

Enabling CORS on Amazon CloudFront with S3 as your Origin Server

Today I was debugging a customer's CloudFront setup to ensure that they were supporting CORS correctly. Amazon has documented the process, but the docs seem to be structured to work as a reference rather than a how-to. I thought I'd document what I had to do to get things working right, so that this serves as a starting point for others to get set up.

As you probably know, enabling CORS is important if you want to catch cross-domain JavaScript errors. If you are using CloudFront as a CDN, you are most likely using a different domain (or subdomain) to serve your files, and will need to set up CORS at CloudFront.

The bulk of the surprises with setting up CORS with CloudFront are with configuring S3 correctly. This is the typical setup for most people (CloudFront using S3 as their Origin Server), so you'll probably have to deal with this first.

Configuring S3

S3 has this unnecessarily complicated "CORS configuration" that you need to create. Here's the steps to get that right:

  • Log into your AWS S3 console, select your bucket, and select "Properties". S3 CORS configurations seem to apply at the level of the bucket, and not the file. I have no clue why.
  • Expand the "Permissions" pane, and click on "Add CORS configuration" or "Edit CORS configuration" depending on what you see.
  • You should already be provided with a default permission configuration XML. (Seriously, Amazon? 2014? XML?) If not, use the following XML to get started.
    <?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>Authorization</AllowedHeader>
        </CORSRule>
    </CORSConfiguration>
    

    You should look at Amazon's docs to see what this configuration means.

    In the course of this debugging exercise, I discovered the hard way that Amazon's XML parser cares about the <?xml ?> declaration, and the xmlns on the root node. If you omit these, Amazon will fail silently, showing you a happy looking green tick! (Can you imagine how hard it was to figure this out?)

  • Once you've saved the configuration, go get a coffee (or other preferred poison) while you wait for S3 to be one with your new configuration, and really internalise it's true meaning. (It takes a couple of minutes. Some sort of caching, I guess.)
  • Test if everything's looking right. This really tripped me up. Turns out, for a really complicated reason, you can't simply hit a URL in your S3 bucket from within your browser and check the "Network" tab in your dev tools. That would be too easy. No, you need to ensure that you specify certain extra headers in your request. Now, it turns out that browsers send this with CORS requests anyway, so you are covered in the real-world, but it's crazy that the dev-experience for the common case isn't what you'd expect. You could use a tool like curl to specify the additional headers needed for a "correct" CORS request:
    $ curl -sI -H "Origin: example.com" -H "Access-Control-Request-Method: GET" https://s3.amazonaws.com/bucket/script.js
    
    HTTP/1.1 200 OK
    Date: Wed, 05 Nov 2014 13:37:20 GMT
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: GET
    Access-Control-Max-Age: 3000
    Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
    Cache-Control: max-age=604800, public
    ...snip...
    
    You should see the "Access-Control-Allow-Origin: *" header, and the "Vary: Origin" header in the output. If you do, you're golden.

With that, you are almost done! CloudFront's configuration is a piece of cake in comparison.

Configuring CloudFront

  • Go to your CloudFront console, select your distribution and go to the "Behaviors" tab.
  • You should already have a "Default" behavior listed there. Select it and hit "Edit".
  • Under the "Whitelist Headers" section, navigate their clunky UI to add the "Origin" header to the whitelist.
  • Save, get another coffee, and wait for this to propagate through CloudFront's caches. This will take some time.
  • Test! Again, you will have to use the process above to make sure you are flipping the right switches within Amazon. That is, use curl (or some HTTP client), and ensure that you specify the extra headers. You should see the "Access-Control-" headers in the response.

There you go! That should get you set up. I can't believe Amazon has made it so complicated to essentially send an additional HTTP header. Well, regardless, I hope this post helps you get set up correctly.

You will also need to modify your script tags to ensure that you catch JS errors correctly. You can read more about that in the docs.

Not catching JS errors yet? You should really give Errorception a shot.

12 comments:

  1. Thanks a million. Especially the curl test helped.

    Rutger

    ReplyDelete
  2. Looks like Whitelist Headers doesn't appear on my Default Cache Behavior Settings. Has it been moved?

    ReplyDelete
    Replies
    1. I just double-checked, and the instructions above are still correct, and AWS hasn't changed their UI. You're probably looking in the wrong place?

      Delete
  3. Spotted: to find Whitelist Headers you need to open Forward Headers dropdown menu and select "Whitelist" otherwise you won't be able to see it.

    ReplyDelete
  4. This has been really helpful. Thanks a ton :)

    ReplyDelete
  5. I am stuck with point what i can put on origin box on cloudfront.
    Please assist me something regarding it

    ReplyDelete
    Replies
    1. I don't think it matters what you set as the origin header, if that's what you mean. It just needs to exist, and presumably be a valid domain.

      Delete
  6. This was super helpful! We were missing the CloudFront whitelisting headers and couldn't figure out why the S3 CORS configuration wasn't enough. =) Thank you very much!

    ReplyDelete
  7. Very helpful article - however - this does NOT work with Safari. Safari does not pass along Origin header for script tags. Meaning, this solution works great for Chrome / Firefox (even IE). But your JS will completely break with Safari.

    ReplyDelete
    Replies
    1. Hey Nick,

      While CORS on reporting script errors don't work in Safari, script execution doesn't break, and things should work just normally (albeit without the CORS posting). Is this not what you see?

      Delete
    2. I can't get my fonts to load in Squarespace in Safari or iOS. Its not a script tag, its a font-face declaration. I've been scratching my head for days. Pretty sure I have configured S3 and Cloudfront correctly. I can't see the Options Preflight headers using cURL and I don't seem to be able to get them in the Network panel either. I just get a 403 in Safari dev tools. Frustrating. I can't even seem to get a straight answer on whether this is possible. Will this ever work or are they just not compatible

      Delete