Background:
This question arose while trying to figure out a solution to my question here
The problem:
Preflight OPTIONS
requests from Firefox are failing with CORS preflight did not succeed
when the request is routed through Cloudflare.
Expected outcome: Firefox should either not send a preflight request at all (like in Chrome), or the request should succeed and the actual content should load.
Facts that may be relevant
- Two identical
OPTIONS
requests seem to be sent, which I understand is a possible cause for this. However if this is the case, I am not sure what the cause would be. OPTIONS
requests are not appearing in my NGINX logs at all.- I have created a WAF rule on Cloudflare that I thought would make sure the OPTIONS request is passed on. That uses the rule
(http.request.method eq "OPTIONS")
to take the actionskip
, and ticked every box (including “additional actions”). I may have gotten this very wrong though, please enlighten me if so. - Firefox can download the resource by pasting the URL into the browser.
- Running the failed Firefox request with
curl
also returned unauthorized:
<h1>Error 401</h1>
<h3>This bucket cannot be viewed</h3>
</div>
<div>
<p id="error-title">You are not authorized to view this bucket</p>
<p>
This bucket does not exist or is not publicly accessible at this
URL. Check the URL of the bucket that you’re looking for or contact
the owner to enable Public access.
</p>
</div>
<div>
<p id="footer-title">Is this your bucket?</p>
<p>
Learn how to enable
<a
href="https://developers.cloudflare.com/r2/data-access/public-buckets/"
>Public Access</a
>
</p>
</div>
</section>
An example of a preflight request that failed:
OPTIONS /littlebug/edz.zip HTTP/2
Host: arts.example.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: GET
Access-Control-Request-Headers: range
Referer: https://admin.example.com/
Origin: https://admin.example.com
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
And the response (note the custom headers that I put in to test Cloudflare):
HTTP/2 403 Forbidden
date: Sun, 17 Mar 2024 22:36:23 GMT
content-type: application/zip
content-length: 16794
vary: Accept-Encoding
cf-cache-status: DYNAMIC
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=kAqV8jlg%2BedhsvfVrb6J3AXMufxZzZEjWZN9k0%2F9dC8VSy8g7%2Fhug6dKnM8XhT9QhXa1C%2BkH06WitFICafs2YHTyUVu3RZuOPCWp1pSh6sFPBqdU3WwDQ3wqNkC4cX%2Fi%2FgI%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
access-control-allow-origin: https://admin.example.com
origin: https://admin.example.com
test-header: Hello World
test-header2: Goodbye world
server: cloudflare
cf-ray: 866076277f75437e-EWR
alt-svc: h3=":443"; ma=86400
X-Firefox-Spdy: h2
And from the same origin, here:
HTTP/2 403 Forbidden
date: Sun, 17 Mar 2024 22:36:23 GMT
content-type: application/zip
content-length: 16794
vary: Accept-Encoding
cf-cache-status: DYNAMIC
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=kAqV8jlg%2BedhsvfVrb6J3AXMufxZzZEjWZN9k0%2F9dC8VSy8g7%2Fhug6dKnM8XhT9QhXa1C%2BkH06WitFICafs2YHTyUVu3RZuOPCWp1pSh6sFPBqdU3WwDQ3wqNkC4cX%2Fi%2FgI%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
access-control-allow-origin: https://admin.example.com
origin: https://admin.example.com
test-header: Hello World
test-header2: Goodbye world
server: cloudflare
cf-ray: 866076277f75437e-EWR
alt-svc: h3=":443"; ma=86400
X-Firefox-Spdy: h2
And for contrast, a GET request for a file of type application/json
from the same domain to the same R2 bucket (no preflight was sent):
GET /littlebug/edz.json HTTP/2
Host: arts.example.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Origin: https://admin.example.com
Connection: keep-alive
Referer: https://admin.example.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
And the response:
HTTP/2 200 OK
date: Sun, 17 Mar 2024 22:36:23 GMT
content-type: application/json
access-control-allow-origin: https://admin.example.com
etag: W/"12c98e03b4d157e0b0ae977b1aa2443a"
last-modified: Sat, 09 Mar 2024 19:38:09 GMT
vary: Origin, Accept-Encoding
access-control-expose-headers: Content-Encoding,Content-Type,Cache-Control,Content-Length
cf-cache-status: DYNAMIC
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=c2rYtwFcs3wyy58bqYNvrDpWU8rp%2F%2F0mcvA6NjSZ8tCZqiSq%2BSC3aa%2FdoqZzyDvD5Truma2X%2FV%2FDaiXpy79x8vGiLXh0VSoWlRj54DKlp1wDxdVyMJcm3w28kzWqFXpLMgY%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 866076258da6437e-EWR
content-encoding: br
alt-svc: h3=":443"; ma=86400
X-Firefox-Spdy: h2
Why would Cloudflare return error 401 to the OPTIONS
request while allowing the actual file to be downloaded?