Access-Control-Allow-Origin — Enable CORS for Static Assets in S3

If you use a public S3 bucket to serve up static assets like images, fonts, etc. to your websites, you must have encountered the infamous CORS error. CORS stands for Cross-Origin Resource Sharing. The error looks something like this in Chrome console:

S3 provides a simple way to enable CORS. Simply add the following CORS policy to the bucket as shown below:

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

Don’t forget to click Save in the above UI. Also try setting AllowedMethod & AllowedHeader to * if you continue to face issues. For more details, see S3 documentation on CORS.

AllowedOrigin Caveat

If you set AllowedOrigin to a URL, not *, make sure you do NOT include the trailing slash. It won’t work if you do:

GOOD — <AllowedOrigin>http://www.example.com</AllowedOrigin>
BAD — <AllowedOrigin>http://www.example.com/</AllowedOrigin>

If you expect calls from both HTTP & HTTPS sites, whitelist them separately, as follows:

<AllowedOrigin>http://*</AllowedOrigin>
<AllowedOrigin>https://*</AllowedOrigin>

The following is OK too:

<AllowedOrigin>*.example.com</AllowedOrigin>

Testing CORS

When testing to see whether your object has CORS headers, make sure your request includes the Origin header. Otherwise S3 won’t include CORS headers in the response. So if you’re using curl to test, it won’t work unless you explicitly provide the Origin header. The same with Postman as well.

A browser (for which the CORS mechanism is meant) will automatically send an Origin header when doing cross-origin HTTP requests through XMLHTTPRequest.

Verify that the request has the Origin header. If the header is missing, Amazon S3 doesn’t treat the request as a cross-origin request, and doesn’t send CORS response headers in the response.

Troubleshooting CORS Issues — S3 Documentation

Tip for Images

If you’re serving images from S3 & using an img tag in your HTML to load them, add the crossorigin tag like this:

<img href="https://my-bucket.s3.amazonaws.com/my-img.png" crossorigin="anonymous">

or like this:

let images = document.getElementsByTagName("img")
  for (let i = 0; i < images.length; i++)
    images[i].setAttribute("crossorigin", "*")

This will cause the browser to send an Origin request header like it does with XMLHTTPRequest. To learn more, see MDN’s documentation of the crossorigin attribute.

Also ensure that you’re not loading a browser cached version of your image. To avoid this, open Chrome DevTools & Disable Cache in the Network tab. Keep DevTools open while you test.

Note about CloudFront

Even if you have a CloudFront distribution in front of your S3 bucket (that’s configured for CORS), you’ll still get the CORS headers in the response if you use the CloudFront URL to the S3 object, instead of its S3 URL. But you must configure CloudFront to forward the Origin header to S3. Also, consider removing MaxAgeSeconds from the bucket’s CORS policy. For more details, see Configuring CloudFront to Respect CORS Settings in the CloudFront documentation.

Set CORS from API

If required, you can set the CORS policy using S3’s PutBucketCors API.

PUT /?cors HTTP/1.1
Host: Bucket.s3.amazonaws.com
Content-MD5: ContentMD5
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
   <CORSRule>
      <AllowedHeader>string</AllowedHeader>
      ...
      <AllowedMethod>string</AllowedMethod>
      ...
      <AllowedOrigin>string</AllowedOrigin>
      ...
      <ExposeHeader>string</ExposeHeader>
      ...
      <MaxAgeSeconds>integer</MaxAgeSeconds>
   </CORSRule>
   ...
</CORSConfiguration>

Examples here: