CORS (Cross Origin Resource Sharing)
This article is here to help you understand CORS (especially as it relates to Infura). We'll talk about how it works and what to do about CORS errors.
References
- Mozilla Developer Network
- Understanding CORS
- Wikipedia
- A guide to solving those mystifying CORS issues
What is CORS?
CORS allows restricted resources on a server to be requested from a webpage that was not served by that server, i.e. a webpage that was served by a different origin. It is enforced by the browser, which provides headers in the request (mainly the origin header) so that the cross-site server can determine if it should authorize the browser to access the requested resource.
CORS is implemented in browsers. It's relevant to browser-based JavaScript, not to backend nodejs (or Python or Ruby) code. While you can do CORS experiments with curl this does NOT simulate the rest of the browser behavior, especially the behavior of not returning the data from the server back to the JavaScript code that made the request; the browser quarantines the data if everything is not right. Nor does using curl show the error message that the browser generates if the response from the server is not correct, and which would normally be visible in the console. Instead, you must interpret the headers yourself. If the request “works” in curl or Postman, that's no guarantee that it will work from a browser.
Some requests are “simple” - GET, HEAD, and some POSTs, i.e. those that have “allowed” content types. The allowed POST content types are either harmless (text/plain) or could be harmful but are legacy types that everyone knows how to deal with already (multipart/form-data).
However, even for the simple requests above, the browser will need to send a preflight if authentication is required, or if other headers are present that are not on the CORS safelisted. For simple requests that only include safelisted headers, the browser doesn't need to run a “preflight” request.
A preflight request is one with the verb OPTIONS plus some headers to ask the server for a response that says if the real request will be allowed.
Here's an example preflight request copied from the browser devtools and represented as a curl command:
curl 'https://ipfs.infura.io:5001/api/v0/add?stream-channels=true&progress=false' \
-X 'OPTIONS' \
-H 'Accept: */*' \
-H 'Accept-Language: en-AU,en-GB-oxendict;q=0.9,en;q=0.8' \
-H 'Access-Control-Request-Headers: authorization' \
-H 'Access-Control-Request-Method: POST' \
-H 'Connection: keep-alive' \
-H 'Origin: http://localhost:3000' \
-H 'Referer: http://localhost:3000/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: cross-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' \
--compressed -v
And here is the corresponding actual request:
curl 'https://ipfs.infura.io:5001/api/v0/add?stream-channels=true&progress=false' \
-H 'Accept: */*' \
-H 'Accept-Language: en-AU,en' \
-H 'Connection: keep-alive' \
-H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFUZB7uL4BhiUxKJW' \
-H 'Origin: http://localhost:3000' \
-H 'Referer: http://localhost:3000/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: cross-site' \
-H 'Sec-GPC: 1' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' \
-H 'authorization: Basic xxxtokenxxx' \
--data-raw $'------WebKitFormBoundaryFUZB7uL4BhiUxKJW\r\nContent-Disposition: form-data; name="file"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\nxxxxBINARY_DATA_bla_blaxxxx'
--compressed -v
NOTE that this is a POST with multipart/form-data which would normally qualify as a simple request, but because there's an authorization header it is no longer “simple” and a preflight request is sent by the browser. The Sec-* headers are not on the CORS safelist either.
curl uses -X POST by default with multipart/form-data.
For browser JavaScript coders, it's worth nothing that there is a list of forbidden request headers. These are request headers that are not allowed to be added or changed by code, because the browser has complete control over these particular headers. See the Fetch API reference.
Note that both Infura/eth and Infura/IPFS API endpoints return an access-control-allow-origin header with the exact same URL as the origin header provided in the request, i.e. not a ‘*' or wildcard. This is because of the CORS rule that disallows authentication if the allowed origin is a wildcard, and Infura often requires authentication, especially for IPFS.
For example, if the origin header is http://localhost:3000 then the same origin will be returned in the access-control-allow-origin header. In fact if the value for the origin header is “whatever” then “whatever” will be returned.
Example of a CORS error
There is only one source of CORS error from Infura. Below is a preflight for an Ethereum endpoint request that includes an authorization header (i.e. the API Secret Key was included).
curl 'https://mainnet.infura.io/v3/c1f6fa004ded489fa95d3d84219e8860' \
-X 'OPTIONS' \
-H 'authority: mainnet.infura.io' \
-H 'accept: */*' \
-H 'accept-language: en-AU,en-GB-oxendict;q=0.9,en;q=0.8' \
-H 'access-control-request-headers: authorization,content-type' \
-H 'access-control-request-method: POST' \
-H 'cache-control: max-age=0' \
-H 'origin: http://localhost:3001' \
-H 'referer: http://localhost:3001/' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: cross-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' \
--compressed -v
Note that a preflight request does not actually include the authorization header, but it does signal that the actual request will include it (see access-control-request-headers above).
The response is:
< HTTP/2 200
< date: Mon, 14 Nov 2022 23:02:37 GMT
< content-length: 0
< vary: Accept-Encoding
< vary: Origin
< vary: Access-Control-Request-Method
< vary: Access-Control-Request-Headers
As you can see, access-control-allow-origin, etc, are missing in the response. The browser will show a CORS error in the console.
And the response when the authorization header is not included is:
< HTTP/2 200
< date: Mon, 14 Nov 2022 23:12:57 GMT
< content-length: 0
< access-control-allow-headers: Content-Type
< access-control-allow-methods: POST
< access-control-allow-origin: http://localhost:3001
< access-control-max-age: 86400
< vary: Accept-Encoding
< vary: Origin
< vary: Access-Control-Request-Method
< vary: Access-Control-Request-Headers
Here, access-control-allow-origin etc. are present. So, if the API secret is included in the request (only from a browser, not from backend code), then the request will fail with a CORS error.
This is by design: the API Secret Key should never be included in browser JavaScript frontend code because it's easily visible there.
Dealing with CORS errors
The bottom line is: if you get a CORS error, then there's a good reason (such as we want to disallow including API secret key in the front end) or changes need to be made on the Infura side to correct them.
Alternatively, you can move your Infura access to your backend if your architecture allows this option. The backend does not send CORS related headers (unless you code them in) and Infura will not send back CORS related responses. Even if it did it's only the browser that makes decisions based on them, so CORS does not come into the picture.
If your architecture does not lend itself to moving your Infura access to the backend, you can use a “CORS proxy”. This takes the requests from your browser, passes them on to Infura, and adds the headers to the response so that the browser is satisfied. This solution is like a mini-backend. It's a backend that handles CORS concerns only. See our guide.
Summary
CORS is enforced in the browser, this is why your backend code or a curl request “works”. Both these environments are not doing any CORS checking, and are not sending CORS headers.
Still encountering errors after trying our solution? Open a support case and provide the code generating the request, or the equivalent curl request, so that we can understand what is happening.