CloudFront S3 origin returned 403 for CORS preflight and the bucket policy change that allowed OPTIONS requests
Blog
Olivia Brown  

CloudFront S3 origin returned 403 for CORS preflight and the bucket policy change that allowed OPTIONS requests

When deploying a web application that integrates with assets hosted on Amazon S3 and distributed via Amazon CloudFront, developers commonly run into Cross-Origin Resource Sharing (CORS) issues—particularly with preflight OPTIONS requests. One particularly frustrating error is the 403 Forbidden response that CloudFront returns when a CORS preflight request to an S3 origin is not properly handled. This issue typically stems from misconfigured or inadequate S3 bucket policies or CORS configurations.

TL;DR

If CloudFront is returning a 403 error for a CORS preflight OPTIONS request to your S3 origin, it’s likely due to an insufficient bucket policy that does not allow public access for OPTIONS. To resolve this, a change to the S3 bucket policy is necessary to explicitly allow these types of requests. Additionally, ensuring your S3 CORS configuration permits OPTIONS methods and required headers is critical. This article explains the root causes and provides the policy fix with explanations.

Understanding the CORS 403 Issue in CloudFront with S3 Origin

Cross-Origin Resource Sharing (CORS) is a browser security feature that restricts web pages from making requests to a different domain than the one that served the original page. When a frontend application deployed on domain frontend.example.com tries to fetch a resource like an image or JSON file from a CloudFront distribution that uses an S3 origin (e.g., d111111abcdef8.cloudfront.net), CORS comes into play.

Modern browsers send a preflight request—an HTTP OPTIONS request—to verify the CORS permissions before sending the actual GET or POST request. If the S3 origin doesn’t respond correctly to this OPTIONS request with appropriate headers, or if CloudFront is misconfigured, the request fails.

When this happens, you’re likely to see errors such as:

  • 403 Forbidden for OPTIONS request
  • Console error: “CORS policy: Response to preflight request doesn’t pass access control check”
  • CloudFront logs showing a response code 403 with no cache hit

Why Does S3 Reject OPTIONS Requests?

Amazon S3 can serve static assets like HTML, JavaScript, CSS, or images. However, when accessed through a CloudFront distribution, it’s CloudFront that communicates with the S3 bucket as the origin server. If S3 receives a method it doesn’t expect—like an OPTIONS request—without the proper CORS or IAM permissions, it will return a 403 Forbidden error.

The most common root causes include:

  • Missing or incorrect CORS configuration on the S3 bucket
  • Bucket policy that does not allow public access to the OPTIONS method
  • CloudFront not configured to forward headers correctly

While the CORS configuration defines what origins and methods are allowed, it is still the S3 bucket policy that enforces permissions. If the policy doesn’t explicitly allow anonymous users to invoke the OPTIONS method, S3 will reject the request before even evaluating the CORS configuration.

Understanding the Role of the Bucket Policy

S3 bucket policies are JSON-based access control rules that define who can do what with the bucket and its contents. In the case of a CORS preflight request, the user is typically an unauthenticated browser making an anonymous request.

Here’s a typical bucket policy that causes the 403 error:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowGetRequests",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

The issue here is that only s3:GetObject is allowed; there’s no permission for s3:ListBucket or more critically, for OPTIONS method—which is interpreted within S3 via a different permission requirement.

Fixing the Problem: Updating the Bucket Policy

To resolve the issue, the bucket policy must be updated to explicitly allow s3:ListBucket if needed, but more importantly allow anonymous OPTIONS requests. The key is to recognize that S3 requires a universal s3:* permission for method handling in preflight.

Here’s an updated bucket policy that resolves the issue:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAllToGetAndOptions",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ],
      "Condition": {
        "IpAddress": { "aws:SourceIp": "0.0.0.0/0" }
      }
    }
  ]
}

While s3:GetObject is required for normal asset access, it’s the inclusion of the root bucket ARN (`arn:aws:s3:::your-bucket-name`) and potentially additional permissions that allow OPTIONS handling correctly via CloudFront.

Note: Some developers report success by simply enabling public access to the object paths and adding s3:GetObject and checking “Block all public access” settings in the S3 console are appropriately disabled.

Properly Configuring S3 CORS Settings

Configuring the CORS policy at the bucket level is also essential. The preflight request expects specific headers and methods to be acknowledged. A correct configuration might look like:

[
  {
    "AllowedHeaders": [
      "*"
    ],
    "AllowedMethods": [
      "GET",
      "HEAD",
      "OPTIONS"
    ],
    "AllowedOrigins": [
      "https://frontend.example.com"
    ],
    "ExposeHeaders": [],
    "MaxAgeSeconds": 3000
  }
]

This configuration ensures that:

  • OPTIONS requests are allowed
  • The requested origin is permitted
  • Any headers requested by the browser are acceptable

Ensure CloudFront is Forwarding OPTIONS Requests Correctly

CloudFront must be configured to forward the OPTIONS method to the S3 origin, or else it will reply with a cached 403 or fail to send the request at all.

To verify this:

  1. Go to CloudFront Distributions
  2. Select your distribution
  3. Edit your origin behavior (usually /)
  4. In Cache Policy and Origin Request Policy, ensure an origin request policy is used that forwards OPTIONS and all headers if needed
  5. Use the CORS-S3Origin preset or a custom policy with required methods and headers

Also ensure caching does not interfere with CORS headers by enabling all header forwarding and disabling caching for OPTIONS requests where appropriate.

Security Considerations

While enabling public access to specific methods and origins is necessary for web functionality, overly permissive configurations should be avoided. Limit:

  • Allowed origins-based domains you actually control
  • Allowed methods (GET, OPTIONS, HEAD usually suffice)
  • Wildcards in headers only when truly necessary

Moreover, you should monitor your usage through AWS CloudTrail or S3 Access Logs to detect any anomalies or misuse of CORS settings.

Conclusion

The 403 Forbidden error for CORS OPTIONS requests through CloudFront with an S3 origin is a classic example of a misaligned configuration between services. With CloudFront acting as a proxy, both the S3 bucket policy and CORS settings must be correctly configured to allow such requests. Updating the bucket policy to allow anonymous access for OPTIONS, and ensuring CloudFront forwards headers and methods properly, resolves the issue in almost all situations.

By understanding how each AWS component plays a role—S3 for access control and response, and CloudFront for request routing and caching—you can confidently deploy static assets for cross-origin use without compromising security or breaking browser behavior.