Search code examples
amazon-web-servicesamazon-s3corsamazon-cloudfront

CORS Access-Control-* headers dropped from AWS Cloudfront Signed URL serving S3 bucket data on a custom domain


I'm serving static content from an S3 bucket via a signed url from Cloudfront that is redirected from my REST API using a 302. I'm trying to retrieve that content via my frontend web app and I'm running into CORS issues related to missing access-control-* headers in my GET request for the asset.

When making a request the initial OPTIONS request returns the expected headers:

Status: 200
access-control-allow-credentials: true
access-control-allow-headers: x-client-version
access-control-allow-methods: GET, HEAD
access-control-allow-origin: https://subdomain.version_number.domain.extension
access-control-expose-headers: Access-Control-Allow-Origin

However the following GET request is missing any of the same Access-Control-Allow-Credentials, Access-Control-Allow-Headers etc are completely missing from the GET request and the console shows:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at [signed_url] (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

Curl works but the browser doesn't

Using curl and setting the correct origin I can see the correct Access-Control-* headers being set:

curl -H "Origin: https://subdomain.version_number.domain.extension" --verbose "[signed_url]"

Response:

> Host: mybucket.anotherdomain.com
> User-Agent: curl/8.5.0
> Accept: */*
> Origin: https://subdomain.version_number.domain.extension
> Access-Control-Request-Method: GET
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200 
< content-length: 0
< date: Fri, 21 Jun 2024 15:22:50 GMT
< access-control-allow-origin: https://subdomain.version_number.domain.extension
< access-control-allow-methods: GET, HEAD
< access-control-expose-headers: Access-Control-Allow-Origin
< access-control-allow-credentials: true
< vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
< server: AmazonS3
< x-cache: Miss from cloudfront
< via: 1.1 [].cloudfront.net (CloudFront)
< x-amz-cf-pop: []
< x-amz-cf-id: []

Cloudfront behaviour settings Cloudfront behaviour settings

Custom policies

I've configured the Cloudfront distribution using the following:

Origin and origin groups: my_s3_bucket
Viewer protocol policy: HTTPS only
Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
Restrict viewer access: Yes -> trusted key group assigned to the bucket

Cache key and origin requests policies Cache Key Policy

Origin request Policy enter image description here

Response headers policy response headers

The S3 bucket CORS config is set to:

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET",
            "HEAD"
        ],
        "AllowedOrigins": [
            "https://subdomain.version_number.domain.extension"
        ],
        "ExposeHeaders": [
            "Access-Control-Allow-Origin"
        ]
    }
]

Cloudfront -> Bucket access policy

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity[ID]"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my_s3_bucket/*"
        }
    ]
}

Solution

  • It turns out that performing a 302 on a get request with credentials: true is not supported by browsers:

    Chrome: https://issues.chromium.org/issues/40290910

    Mozilla Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1346749

    According to the Mozilla docs this approach of redirecting traffic with credentials is not recommended and should be refactored:

    The CORS request was responded to by the server with an HTTP redirect to a URL on a different origin than the original request, which is not permitted during CORS requests.

    For example, if the page https://service.tld/fetchdata were requested, and the HTTP response is "301 Moved Permanently", "307 Temporary Redirect", or "308 Permanent Redirect" with a Location of https://anotherservice.net/getdata, the CORS request will fail in this manner.

    To fix the problem, update your code to use the new URL as reported by the redirect, thereby avoiding the redirect.

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