Client-side fetch CORS issue

I have a Gatsby site that utilizes Apollo Client to make dynamic graphql queries to a Wordpress CMS on the client side (e.g. to fetch real time price and stock availability updates).

I’m using wp-graphql-cors on the Wordpress side that restricts CORS access to approved domains, and have included the following domains:

http://localhost:8000 (local development)
https:/___MY-GATSBY-SITE___.gtsb.io (Gatsby Cloud)
https://___MY-NETLIFY-SITE___.netlify.app (Netlify)

This works fine in localhost and on Gatsby Cloud, but throws a CORS issue on Netlify. The error I receive on the client side console is:

Access to fetch at 'https://___MY-CMS-ENDPOINT___/graphql?currency=USD' from origin 'https://___MY-NETLIFY-SITE___.netlify.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://___MY-CMS-ENDPOINT___' that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Here is a comparison between localhost, Gatsby Cloud and Netlify request headers:

Via localhost:8000

Request:

POST /graphql?currency=USD HTTP/2
Host: ___MY-CMS-ENDPOINT___
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-CA,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:8000/
content-type: application/json
Origin: http://localhost:8000
Content-Length: 884
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
TE: trailers

Response:

HTP/2 200 OK
server: nginx
date: Fri, 27 Aug 2021 20:55:49 GMT
content-type: application/json; charset=UTF-8
x-powered-by: PHP/7.4.9
expires: Thu, 19 Nov 1981 08:52:00 GMT
cache-control: no-store, no-cache, must-revalidate
pragma: no-cache
access-control-allow-origin: http://localhost:8000
access-control-allow-headers: Authorization, Content-Type, woocommerce-session, graphql_cors_settings
access-control-max-age: 600
x-robots-tag: noindex
x-content-type-options: nosniff
x-hacker: If you're reading this, you should visit github.com/wp-graphql/wp-graphql and contribute!
access-control-allow-credentials: true
access-control-expose-headers: woocommerce-session
strict-transport-security: max-age=31536000
X-Firefox-Spdy: h2

Via Gatsby Cloud

Request:

POST /graphql?currency=USD HTTP/2
Host: ___MY-CMS-ENDPOINT___
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-CA,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: https://___MY-GATSBY-SITE___.gtsb.io/
content-type: application/json
Origin: https://___MY-GATSBY-SITE___.gtsb.io
Content-Length: 1973
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
TE: trailers

Response:

HTTP/2 200 OK
server: nginx
date: Fri, 27 Aug 2021 21:12:30 GMT
content-type: application/json; charset=UTF-8
x-powered-by: PHP/7.4.9
expires: Thu, 19 Nov 1981 08:52:00 GMT
cache-control: no-store, no-cache, must-revalidate
pragma: no-cache
access-control-allow-origin: https://___MY-GATSBY-SITE___.gtsb.io
access-control-allow-headers: Authorization, Content-Type, woocommerce-session, graphql_cors_settings
access-control-max-age: 600
x-robots-tag: noindex
x-content-type-options: nosniff
x-hacker: If you're reading this, you should visit github.com/wp-graphql/wp-graphql and contribute!
access-control-allow-credentials: true
access-control-expose-headers: woocommerce-session
strict-transport-security: max-age=31536000
X-Firefox-Spdy: h2

Via Netlify

Request:

POST /graphql?currency=USD undefined
Host: ___MY-CMS-ENDPOINT___
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-CA,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
content-type: application/json
Origin: https://___MY-NETLIFY-SITE___.netlify.app
Content-Length: 120
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site

Response:

N/A

I’m also seeing that the Referrer Policy on the network event as follows:

strict-origin-when-cross-origin (localhost + Gatsby Cloud)
same-origin (Netlify)

No special configuration on the Netlify site, no custom headers, or anything else that I can think of.

Any ideas on how to troubleshoot this further would be great, thanks!

Hi @ardiewen,

This is hard to debug as the headers are being sent by the Wordpress CMS and not Netlify. From what I’ve understood, Netlify is simply running your client-side JS just like localhost and Gatsby Cloud. Plus, we don’t even have the URLs to test.

My first point to test would be to try accessing the URL from Netlify with all CORS allowed. If that works well and it’s only after you add CORS headers that this doesn’t work, this would mean a problem with the CORS setup.

Hi @hrishikesh , I was able to resolve this myself.

Netlify, for some reason, is setting Referrer Policy: same-origin in the header of all my client-side javascript requests.

This is why Gatsby Cloud and localhost work while the Netlify builds do not. My CORS configuration is set up properly on the server-side.

Adding a custom header entry in either netlify.toml, or in my case, gatsby-plugin-netlify will resolve the issue.

Something like this:

[[headers]]
 for = "/*"
 [headers.value]
   Referrer-Policy = "strict-origin-when-cross-origin"

It would be great if there could be some sort of documentation with default header values that could be referenced for better troubleshooting.

1 Like

I would even like this to be standardized, for server requests. Thanks for the writeup! @hrishikesh #features