109

My application stores images on S3 and then proxies them through Cloudfront. I'm excited to use the new S3 CORS support so that I can use HTML5 canvas methods (which have a cross-origin policy) but can't seem to configure my S3 and Cloudfront correctly. Still running into "Uncaught Error: SECURITY_ERR: DOM Exception 18" when I try to convert an image to a canvas element.

Here's what I have so far:

S3

<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>MY_WEBSITE_URL</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
  <CORSRule>
    <AllowedOrigin>MY_CLOUDFRONT_URL</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
    </CORSRule>
  </CORSConfiguration>

Cloudfront

Origins

Origin Protocol Policy: Match Viewer

HTTP Port: 80

HTTPS Port: 443

Behaviors

Origin: MY_WEBSITE_URL

Object Caching: Use Origin Cache Headers

Forward Cookies: None

Forward Query Strings: Yes

Is there something I'm missing here?

UPDATE : Just tried changing the headers to

<AllowedHeader>Content-*</AllowedHeader>
<AllowedHeader>Host</AllowedHeader>

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

Still no go.

UPDATE: MORE INFO ON REQUEST

Request
URL:https://d1r5nr1emc2xy5.cloudfront.net/uploaded/BAhbBlsHOgZmSSImMjAxMi8wOS8xMC8xOC81NC80Mi85NC9ncmFzczMuanBnBjoGRVQ/32c0cee8
Request Method:GET
Status Code:200 OK (from cache)

UPDATE

I think maybe my request wasn't correct, so I tried enabling CORS with

img.crossOrigin = '';

but then the image doesn't load and I get the error: Cross-origin image load denied by Cross-Origin Resource Sharing policy.

tijko
  • 7,599
  • 11
  • 44
  • 64
kateray
  • 2,066
  • 4
  • 18
  • 23
  • can you post your post request here ?. as in your policy and parameters passed in post request while uploading to s3. – Avichal Badaya Sep 11 '12 at 19:09
  • why the POST request as opposed to the GET request? – kateray Sep 11 '12 at 19:17
  • ok, can you give information about get request ? – Avichal Badaya Sep 11 '12 at 19:20
  • it's just an 'src' - is there some other way i should be formatting the request? – kateray Sep 12 '12 at 15:45
  • are you able to store the images on s3 ?. is this the problem you are getting while retrieving the image? try with link from details section of s3 object. it will be something like https://s3.amazonaws.com//... and check if you still get the error.I had implemented exactly same thing using CORS, so if you give me more details , I can help you out. – Avichal Badaya Sep 12 '12 at 18:10

9 Answers9

176

On June 26, 2014 AWS released proper Vary: Origin behavior on CloudFront so now you just

  1. Set a CORS Configuration for your S3 bucket including

    <AllowedOrigin>*</AllowedOrigin>

  2. In CloudFront -> Distribution -> Behaviors for this origin

    • Allowed HTTP Methods: +OPTIONS
    • Cached HTTP Methods +OPTIONS
    • Cache Based on Selected Request Headers: Whitelist the Origin header.
  3. Wait for ~20 minutes while CloudFront propagates the new rule

Now your CloudFront distribution should cache different responses (with proper CORS headers) for different client Origin headers.

Charney Kaye
  • 3,667
  • 6
  • 41
  • 54
Brett
  • 3,478
  • 1
  • 22
  • 23
  • 1
    Nice. This also looks like it solves issues serving up assests via both HTTP & HTTPS with CORS. – Ray Jul 22 '14 at 17:36
  • 7
    FWIW, I also had to change the caching behavior to vary by "allowed HTTP methods" including OPTIONS. – welegan Aug 25 '14 at 15:43
  • absolute life saver - this worked for my issues with Glyphicons on S3/Cloudfront & Heroku – cman77 Apr 16 '15 at 01:36
  • 1
    You're missing a step. The browser will send an OPTIONS request to validate the Origin header is allowed. You should therefore click 'GET, HEAD, OPTIONS' and not just the default 'GET, HEAD', ensuring that Options is *not* cached (otherwise the Origin will always be the same!) – Lee Benson Dec 23 '16 at 11:14
  • 6
    According to the [AWS documentation on this](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors), you should also whitelist `Access-Control-Request-Headers` and `Access-Control-Request-Method` if using an S3 origin and you want `OPTIONS` cached (which generally I believe you will want). – user Jun 01 '18 at 15:17
  • Instead of waiting 20 minutes or longer in step 3. You may create invalidation on cloudfront to force it refresh – Bird Bird Jun 29 '19 at 10:47
  • 1
    Sometimes you may need to reset the cache in your local testing browser. The fixes worked for me, but failed to propagate to my browser due to the caching issue :) – MathBunny Apr 21 '20 at 14:40
  • 1
    @MathBunny funny, but your comment for me proved the most useful. Thanks! Was going over everything about the 100th time, and trying incognito as well (Chrome). Then I decided to try completely different browser (Firefox) and it worked. After some while it started working also in Chrome. – matiss.andersons Sep 06 '20 at 00:27
78

To complement @Brett's answer. There are AWS documentation pages detailing CORS on CloudFront and CORS on S3.

The steps detailed there are as follows:

  1. In your S3 bucket go to Permissions -> CORS configuration
  2. Add rules for CORS in the editor, the <AllowedOrigin> rule is the important one. Save the configuration. enter image description here
  3. In your CloudFront distribution go to Behavior -> choose a behavior -> Edit
  4. Depending on whether you want OPTIONS responses cached or not, there are two ways according to AWS:
  • If you want OPTIONS responses to be cached, do the following:
    • Choose the options for default cache behavior settings that enable caching for OPTIONS responses.
    • Configure CloudFront to forward the following headers: Origin, Access-Control-Request-Headers, and Access-Control-Request-Method.
  • If you don't want OPTIONS responses to be cached, configure CloudFront to forward the Origin header, together with any other headers required by your origin

enter image description here

And with that CORS from CloudFront with S3 should work.

Christian Eriksson
  • 2,038
  • 2
  • 22
  • 28
  • 30
    When should someone cache or not cache the OPTIONS responses? – Fabien Snauwaert Jan 07 '19 at 11:52
  • 1
    This should work even if the bucket policy gives read permissions only to the cloudfront distribution, right? Adding this since you highlight the public permissions in the image. – ChrisOdney Oct 03 '19 at 13:51
  • Omg, thank you so much for this answer. We've been running our cloudfront incorrectly for few months now. Huge props for this! – Capaj Mar 25 '20 at 09:16
  • Remember when testing to see if your changes worked that you need to send an "Origin: " header with your request, or else you won't get the new headers back. – Kurt Jul 09 '20 at 10:50
37

2022 answer:

  1. Go to your S3 bucket -> Permissions
  2. Scroll down to Cross-origin resource sharing (CORS)
  3. Apply policy:
[
    {
        "AllowedHeaders": [],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

This will allow GET request from all origins. Modify according to your project's needs.

  1. Go to your CloudFront distribution -> Behaviors -> Edit (in my case I had only one Behavior)

  2. Scroll down to Cache key and origin requests

  3. Select Cache policy and origin request policy (recommended)

  4. Under Origin request policy - optional select CORS-CustomOrigin

  5. Save Changes

Done!

Nikolay Dyankov
  • 6,491
  • 11
  • 58
  • 79
9

March 2023 - this works for me.

S3 Bucket config

Cross-origin resource sharing (CORS) policy for S3 bucket (bucket details > permissions). Adjust AllowedOrigins, AllowedHeaders for your project (you can use * during setup testing).

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET",
            "HEAD"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

AWS Console for bucket CORS

Cloudfront CDN config

Go to Cloudfront distribution > Edit behavior (in most cases there is only one per distribution)

CDN Behavior config

michal-michalak
  • 827
  • 10
  • 6
  • Thanks. This was very useful indeed. One thing that tripped me up: to get CloudFront to emit the `Access-Control-Allow-Origin: *` response header that I was after, you have to include an `Origin` header in the request. Without it, the only extra thing CloudFront wants to emit is `Vary: Origin`. – Bellarmine Head Jul 22 '23 at 16:21
  • Origin request policy Core-CustomOrigin didn;t work for me, if your cloudfront is connected to S3 then choosing CORS-S3Origin worked. changes were reflected immediately for me. – Gulam Hussain Aug 29 '23 at 06:12
7

UPDATE: this is no longer true with recent changes on CloudFront. Yippee! See the other responses for the details. I'm leaving this here for context/history.

Problem

CloudFront does not support CORS 100%. The problem is how CloudFront caches the response to the request. Any other request for the same URL after that will result in the cached request no matter the origin. The key part about that is that it includes the response headers from the origin.

First request before CloudFront has anything cached from Origin: http://example.com has a response header of:

Access-Control-Allow-Origin: http://example.com

Second request from Origin: https://example.com (note that it is HTTPS not HTTP) also has the response header of:

Access-Control-Allow-Origin: http://example.com

Because that is what CloudFront cached for the URL. This is invalid -- the browser console (in Chrome at least) will show a CORS violation message and things will break.

Workaround

The suggested work around is to use different URLs for different origins. The trick is to append a unique query string that is different so that there is one cached record per origin.

So our URLs would be something like:

http://.../some.png?http_mysite.com
https://.../some.png?https_mysite.com

This kind of works but anyone can make your site work poorly by swapping the querystrings. Is that likely? Probably not but debugging this issue is a huge hassle.

The right workaround is to not use CloudFront with CORS until they fully support CORS.

In Practice

If you use CloudFront for CORS, have a fallback to another method that will work when CORS does not. This isn't always an option but right now I'm dynamically loading fonts with JavaScript. If the CORS-based request to CloudFront fails, I fall back to a server-side proxy to the fonts (not cross origin). This way, things keep working even though CloudFront somehow got a bad cached record for the font.

Cymen
  • 14,079
  • 4
  • 52
  • 72
6

As a completion on the previous answer, I would like to share AWS steps on how to enable CORS. I found it very useful, providing additional links: https://aws.amazon.com/premiumsupport/knowledge-center/no-access-control-allow-origin-error/

Also, something that you should consider when testing your changes, other than CloudFront deploy delay, is the browser cache. I suggest using different sessions for incognito when testing your changes.

  • Thanks! That walkthrough was realy useful. Especially the curl command you can use to test if you are configured correctly: `curl -H "origin: example.com" -v "https://www.anything.net/video/call/System.generateId.dwr"` – checketts Jun 02 '21 at 19:34
5

Posting some of the non-trivial configurations that I did to make it work:

  1. Assign custom domain to cloudfront such that the custom domain is a subdomain from where your app's frontend will run. In OP's case, he is using localhost:3000; most probably he is testing on his dev setup, but he must deploy this app at some domain: let's call this 'myapp.com'. So, he can assign a custom domain, say cdn.myapp.com to point to blah.cloudfront.net. You will need to create/import custom SSL certificate for the new custom domain; default cloudfront certificate won't work.

  2. Refer to this: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html enter image description here

enter image description here In the Cloudfront Distribution snap, the first row is having no custom domain, hence an empty CNAMEs column. The second one is having a custom domain, hence we have that one printed over there. You can verify that your custom domain got pointed to the cloudfront distribution this way.

  1. Cloudfront behaviour: I am assuming you have already set up trusted key group as at this point, you already have the signed cookie with you. HOWEVER, You will need to create custom Cache Policy and Origin Request Policy. enter image description here See the following screenshots of the custom Cache Policy:enter image description hereand Origin Request Policy:enter image description here The thing to notice is that you will need to whitelist these Headers: Origin, Access-Control-Request-Method, Access-Control-Allow-Origin, Access-Control-Request-Headers. (You might notice that Access-Control-Allow-Origin is not in the dropdown; just go ahead and type it!). Also, allow all cookies.

  2. S3 CORS configuration: Go to the S3 bucket and click on the permissions tab. Scroll down to the CORS configuration. Disclaimer: I just pasted what worked for me. The rationale behind this was that this S3 was going to be accessed by either CDN or app in my scenario. I tried putting '*' being lenient, but CORS policy on Chrome complained that I cannot use a wildcard entry in AllowedOrigins!

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "GET",
            "HEAD",
            "DELETE"
        ],
        "AllowedOrigins": [
            "cdn.myapp.com",
            "myapp.com",
            "https://cdn.myapp.com",
            "https://myapp.com"
        ],
        "ExposeHeaders": [
            "ETag"
        ]
    }
]
  1. react-player: I am using react-player like this (note forceHLS option being set, but it is again specific to my use case. I think this is not mandatory in general)
<ReactPlayer
    className="react-player"
    url={url}
    controls={controls}
    light={light}
    config={
      {
        file: {
          forceHLS: true,
          hlsOptions: {
            xhrSetup: function (xhr, url) {
              xhr.withCredentials = true; // send cookies
            },
          },
        },
      }
    }
    playIcon={<PlayIcon />}
    width="100%"
    height="100%"
  />
Ankit Shubham
  • 2,989
  • 2
  • 36
  • 61
5

I followed AWS documentation:

Then I used aws cdk to do it for me. Full source here: https://github.com/quincycs/quincymitchell.com

const myBucket = new Bucket(this, 'bucket', {
  bucketName: `prod-${domainName}`,
  cors: [{
    allowedMethods: [HttpMethods.GET],
    allowedOrigins: ['*'],
    allowedHeaders: ['*']
  }],
  enforceSSL: true,
  blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
  removalPolicy: RemovalPolicy.RETAIN
});
const mycert = Certificate.fromCertificateArn(this, 'certificate', ssmCertArn);

new Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: new S3Origin(myBucket),
    viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    originRequestPolicy: OriginRequestPolicy.CORS_S3_ORIGIN,
    responseHeadersPolicy: ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS,
    allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS, // needed for cors
    cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS, // needed for cors
  },
  defaultRootObject: 'index.html',
  domainNames: [domainName, `www.${domainName}`],
  certificate: mycert
});
Quincy
  • 1,710
  • 14
  • 20
  • Thank you for this. I had everything but the `allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS` part. This is working perfectly for me now. – JoshStrange Jan 23 '23 at 02:24
0

An additional reason for CORS errors could be the HTTP to HTTPS redirect configured in CloudFront.

According to documentation redirects to different origin are not allowed in CORS requests.

As an example, if you will try to access some URL http://example.com what has cloudfront rule to redirect HTTP to HTTPS, you will get a CORS error, since https://cloudfront.url is considered by the browser as a different origin.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSExternalRedirectNotAllowed

Evgalak
  • 51
  • 4