3

In an Angular 9 app, when I add this image caching snippet to ngsw-config.json, I get a CORS error and images don't display. If I remove this snippet the app works correctly (with no errors).

Access to fetch at 'https://assets.myurl.net/images/banner.jpg' from origin 'https://localhost:8100' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

ngsw-config.json (snippet):

"assetGroups": [{
    "name": "cdn",
    "installMode": "lazy",
    "updateMode": "lazy",
    "resources": {
        "urls": [
            "https://assets.myurl.net/**"
        ]
    }
}]

Images are stored in an AWS S3 bucket with CORS configuration:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
  • Cloudflare is being used as a CDN (and DNS).
  • The app is on the latest 9.x releases: @angular 9.2.1, @angular/service-worker 9.1.12.
  • Adding crossorigin="anonymous" to img and picture tags didn't seem to help.

Steps to consistently reproduce the error in Chrome/PC:

  1. Clear browser cache
  2. Open browser private/incognito
  3. Go to website -- images show okay
  4. Click refresh -- images no longer show - CORS errors in browser console

UPDATE

Based on this post (S3 not returning Access-Control-Allow-Origin headers?) the issue appears to be that the service worker does not send an Origin in the request header (so S3 doesn't send access-control headers).

Service Worker Request (copied from Chrome console):

Request URL: https://assets.myurl.net/images/banner.jpg
Referrer Policy: strict-origin-when-cross-origin
Provisional headers are shown
Referer: https://localhost:8100/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36

First of all, make sure an Origin header with every request. If no Origin header is sent, S3 won't send access-control headers, as S3 deems them irrelevant (and typically, they are). A browser (for which the CORS mechanism is meant) will automatically send an Origin header when doing cross-origin HTTP requests through XMLHTTPRequest.

Based on this post (S3 - Access-Control-Allow-Origin Header) the S3 cors script has been updated to include:

<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>

Adding crossorigin="anonymous" to img tags doesn't change the service worker request header. So this doesn't fix the problem.

Cloudflare cache has been purged.

This obscure post (https://community.cloudflare.com/t/access-control-allow-origin-headers-stripped/158102/2) includes:

If you add or change CORS configuration at your origin web server, purging the Cloudflare cache by URL does NOT update the CORS headers.

I cache busted the banner.jpg image by adding a random querystring (i.e. banner.jpg?q=abc). The banner image always displays correctly (while the other images still show cors errors). The service worker cache in Chrome console Application Tab > Cache Storage shows that the image as cached. Cloudflare caching configuration "Caching Level" = Standard "delivers a different resource each time the query string changes".

cached

However, if I upload a new image to S3 banner01.jpg (i.e. not already cached by Cloudflare) I do get a cors error showing this image. I tried this three additional times by uploading different image names and did not get a cors error. Interestingly, an hour later even the new images are showing cors errors.

A curl command (that does not specify Origin in header) runs successfully on my local PC. The response does not contain CORS headers. e.g.

curl -v "https://assets.myurl.net/images/banner.jpg"

Adding origin to the curl command does return CORS headers. e.g.

curl -v --header "Origin: https://www.example.com" "https://assets.myurl.net/images/banner.jpg"

access-control-allow-origin: https://www.example.com
access-control-allow-methods: GET, HEAD
access-control-allow-credentials: true

UPDATE 2

I can't explain why, but Chrome now returns cors headers in the Angular Service Worker fetch response. The request does NOT include an Origin header.

accept-ranges: bytes
access-control-allow-methods: GET, HEAD
access-control-allow-origin: *

However, Safari does not. All browsers appear to be working okay but Safari still shows cors errors.

Matthew
  • 2,871
  • 5
  • 34
  • 59
  • Hi, are you sure you don't need to allow "OPTIONS" too for preflight requests ? – millenion Sep 10 '20 at 01:25
  • The wording in this post is a little vague (https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html) but for rule "*" it states "the rule also allows all headers in a preflight OPTIONS request". So I've added this cors rule to S3 and updated the question. – Matthew Sep 10 '20 at 16:34
  • Some people are saying that they changed their allowed origin from " * " to "https:// * " and "http:// * ", give it a try. – millenion Sep 10 '20 at 18:09
  • And by the way, are you sure that your OPTIONS headers is not cached by cloudfront ? – millenion Sep 10 '20 at 18:16
  • Agreed - I think Cloudflare is caching cors headers and is using the old headers from the old S3 cors settings. Nothing I've tried seems to clear this cache (I can only rename images to work around the problem). https://support.cloudflare.com/hc/en-us/articles/200308847-Using-cross-origin-resource-sharing-CORS-with-Cloudflare – Matthew Sep 10 '20 at 18:27
  • I tried chaning s3 AllowedOrigin to https://localhost but still get cors issues. However, it's hard to test if Cloudflare is using cors headers. Or localhost may not be allowed here. – Matthew Sep 10 '20 at 20:09
  • About the Origin header: A service worker doesn’t control whether or not an Origin header is added to a request. The browser controls it. And if the browser were not sending an Origin header in the request, the browser wouldn’t be logging that *No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled* error message. In other words, if the browser is logging that error message, it means the browser sent an Origin header in the request. – sideshowbarker Sep 10 '20 at 23:36
  • Interesting! Odd - that I can see the service worker "get" in the console but not see the Origin in the request header. I've reviewed the Angular service worker documentation again and can't see anything that allows adjusting the request mode (or to include an Origin). – Matthew Sep 11 '20 at 15:27

1 Answers1

0

Go to your S3 bucket. Click on Permissions, then CORS configuration. If you have a CORS configuration, change the allowed origin from this:

<AllowedOrigin>*</AllowedOrigin>

TO

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

If you don't have a CORS configuration, add a config like this:

<CORSConfiguration>
<CORSRule>
    <AllowedOrigin>http://*</AllowedOrigin>
    <AllowedOrigin>https://*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
Leon Grin
  • 889
  • 8
  • 10
  • This is basically the configuration that exists in the S3 bucket now. But it's not clear if there's something here that specifically fixes the CORS errors in Safari and on iOS devices. Based on the notes in the question above I have this working across all other browsers and devices. – Matthew Sep 21 '20 at 17:32
  • This simple change from * to https://www.example.com is what fixed my problem with Safari. I thought that having your full website URL on AllowedOrigin would solve the problem for you too. – Leon Grin Sep 24 '20 at 13:48