How to Fix CORS “No ‘Access-Control-Allow-Origin’ Header” Errors for S3/CloudFront SVG Assets in Chrome


2 views

When serving SVG assets from Amazon S3 through CloudFront, many developers encounter a puzzling issue where Chrome inconsistently throws CORS errors:

XMLHttpRequest cannot load 
https://d79i1fxsrar4t.cloudfront.net/assets/img/feature-icons/documentation.08e71af6.svg.
No 'Access-Control-Allow-Origin' header is present on the requested resource.

What makes this particularly frustrating is that the behavior appears random - working after hard refreshes (Command+Shift+R) but failing on normal page loads, while working perfectly in some browsers but not others.

First, let's ensure your S3 CORS configuration follows best practices. While your current setup looks correct:

<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>

There are several subtle improvements we can make:

<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>https://*.smartystreets.com</AllowedOrigin>
    <AllowedOrigin>https://smartystreets.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
    <ExposeHeader>ETag</ExposeHeader>
  </CORSRule>
</CORSConfiguration>

Your CloudFront behavior settings should include these critical elements:

  • Allowed HTTP Methods: GET, HEAD, OPTIONS
  • Cached HTTP Methods: GET, HEAD, OPTIONS
  • Forward Headers: Whitelist with these headers:
    • Origin
    • Access-Control-Request-Headers
    • Access-Control-Request-Method
  • Object Caching: Use origin cache headers
  • Compress Objects Automatically: Yes

The key insight here is that Chrome handles cached CORS responses differently than other browsers. When Chrome encounters a cached response:

  1. It may not re-validate CORS headers if the cached response is considered fresh
  2. The Vary header behavior differs between browsers
  3. Preflight requests may be skipped for cached resources

Here's a JavaScript workaround that forces fresh requests:

// Append timestamp to break cache
function fetchSvgWithCacheBust(url) {
  const timestamp = new Date().getTime();
  return fetch(${url}?cb=${timestamp}, {
    headers: {
      'Cache-Control': 'no-cache'
    }
  });
}

Beyond configuration changes, these server-side adjustments solve the issue permanently:

// Example: Express middleware to ensure proper headers
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://smartystreets.com');
  res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Vary', 'Origin');
  next();
});

For CloudFront-specific solutions:

  1. Create a new cache behavior in CloudFront that specifically handles SVG files:
    • Path pattern: *.svg
    • Forward all headers (not just whitelist)
    • Set Minimum TTL to 0
  2. Invalidate your CloudFront cache: /*
  3. Consider using Lambda@Edge to manipulate headers

To properly diagnose CORS issues, use these diagnostic commands:

# Check CORS headers with curl
curl -H "Origin: https://smartystreets.com" \
  -H "Access-Control-Request-Method: GET" \
  -H "Access-Control-Request-Headers: content-type" \
  -X OPTIONS -v https://your.cloudfront.net/path/to.svg

# Chrome debug flags to try
google-chrome --disable-web-security --user-data-dir=/tmp/chrome-debug

Remember that proper CORS implementation requires coordination between:
- S3 bucket policies
- CloudFront behaviors
- Origin response headers
- Browser caching mechanisms


Recently, while working on smartystreets.com, I encountered a puzzling CORS issue where SVG assets loaded via jQuery from CloudFront (fronting S3) would fail inconsistently across browsers. Chrome showed the most persistent problems, with Firefox working perfectly while Opera completely failed.

The errors appeared as:

XMLHttpRequest cannot load 
https://d79i1fxsrar4t.cloudfront.net/assets/img/feature-icons/documentation.08e71af6.svg.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'https://smartystreets.com' is therefore not allowed access.

What made this particularly frustrating was the inconsistent behavior:

  • Hard refresh (Command+Shift+R) often resolved the issue
  • Normal refreshes maintained the errors
  • Different browsers showed varying levels of success

My S3 CORS configuration was properly set:

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

CloudFront was configured with:

  • Allowed Methods: GET, HEAD, OPTIONS
  • Whitelisted Headers: Access-Control-Request-Headers, Access-Control-Request-Method, Origin
  • Cache Behavior: Respect Origin Headers

After extensive testing, I discovered the issue stems from how CloudFront caches responses to OPTIONS requests (preflight) differently from GET requests. The key findings:

  1. CloudFront caches the first response it receives for a given resource
  2. If the first request doesn't include proper CORS headers, subsequent requests fail
  3. Different browsers send different headers, explaining the inconsistent behavior

Here's what worked:

# Invalidate all cached objects in CloudFront
aws cloudfront create-invalidation \
    --distribution-id YOUR_DISTRIBUTION_ID \
    --paths "/*"

Additionally, I modified the S3 CORS configuration to be more explicit:

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

To avoid similar problems:

  1. Always test CORS configurations with curl first:
curl -v -H "Origin: https://yourdomain.com" \
     -H "Access-Control-Request-Method: GET" \
     -X OPTIONS https://your.cloudfront.url/resource
  1. Implement proper cache headers in your origin responses
  2. Consider using Lambda@Edge to manipulate headers at the CDN level

For more control, you can use Lambda@Edge to ensure proper headers:

exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;
    response.headers['access-control-allow-origin'] = [
        { key: 'Access-Control-Allow-Origin', value: '*' }
    ];
    response.headers['access-control-allow-methods'] = [
        { key: 'Access-Control-Allow-Methods', value: 'GET, HEAD, OPTIONS' }
    ];
    callback(null, response);
};