Update:
None of this is needed anymore as cloudfront now has proper support for this. Refer to:
https://aws.amazon.com/about-aws/whats-new/2021/11/amazon-cloudfront-supports-cors-security-custom-http-response-headers/ https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-response-headers.html
Previous response:
This is a bit more complicated than the accepted answer indicates.
The CORS support when using Cloudfront + S3 is actually implemented in S3 and it works like this according to Amazon:
The request's Origin header must match an AllowedOrigin element.
The request method (for example, GET or PUT) or the Access-Control Request-Method header in case the of a preflight OPTIONS request must be one of the AllowedMethod elements.
Every header listed in the request's Access-Control-Request-Headers header on the preflight request must match an AllowedHeader element.
What may not be clear is that if no Origin header is sent by the client, then this processing will not happen. And we're using Cloudfront in front which, if you're just hosting static assets, you've probably set up to ignore all headers.
The result is that if the first request to each file from a specific edge node doesn't include the Origin header, it will cache the response without the Access-Control-Allow-Origin header, resulting in CORS failures.
The result is that the first incoming request will determine which headers are returned for all requests until the cache expires.
There are ways to fix/workaround this.
- Enable conditional caching based on the "Origin" header.
This works fine if you expect only a few or a single origin, but otherwise your caching ratio could become really bad.
- Use Lambda@edge to forcibly set the headers, this can be done just once for each origin (S3) request.
Fully flexible, but adds overhead and cost.
- Override the "Origin" header to a dummy value for every request.
This is done by inserting some random domain name in the "Origin Custom Headers". Anything like "example.org" will work fine, this will cause the S3 processing to always run and if configured correctly S3 will then return "Access-Control-Allow-Origin: *".
This is only really useful in the "Access-Control-Allow-Origin: *" case and it's a bit of a hack, but it's probably the best current solution when hosting static assets on cloudfront + S3.