This answer covers a lot of ground, so it’s divided into three parts:
- How to use a CORS proxy to avoid “No Access-Control-Allow-Origin header” problems
- How to avoid the CORS preflight
- How to fix “Access-Control-Allow-Origin header must not be the wildcard” problems
How to use a CORS proxy to avoid “No Access-Control-Allow-Origin header” problems
If you don’t control the server your frontend code is sending a request to, and the problem with the response from that server is just the lack of the necessary Access-Control-Allow-Origin
header, you can still get things to work—by making the request through a CORS proxy.
You can easily run your own proxy with code from https://github.com/Rob--W/cors-anywhere/.
You can also easily deploy your own proxy to Heroku in just 2-3 minutes, with 5 commands:
git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere/
npm install
heroku create
git push heroku master
After running those commands, you’ll end up with your own CORS Anywhere server running at, e.g., https://cryptic-headland-94862.herokuapp.com/
.
Now, prefix your request URL with the URL for your proxy:
https://cryptic-headland-94862.herokuapp.com/https://example.com
Adding the proxy URL as a prefix causes the request to get made through your proxy, which:
- Forwards the request to
https://example.com
.
- Receives the response from
https://example.com
.
- Adds the
Access-Control-Allow-Origin
header to the response.
- Passes that response, with that added header, back to the requesting frontend code.
The browser then allows the frontend code to access the response, because that response with the Access-Control-Allow-Origin
response header is what the browser sees.
This works even if the request is one that triggers browsers to do a CORS preflight OPTIONS
request, because in that case, the proxy also sends the Access-Control-Allow-Headers
and Access-Control-Allow-Methods
headers needed to make the preflight succeed.
How to avoid the CORS preflight
The code in the question triggers a CORS preflight—since it sends an Authorization
header.
https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
Even without that, the Content-Type: application/json
header will also trigger a preflight.
What “preflight” means: before the browser tries the POST
in the code in the question, it first sends an OPTIONS
request to the server, to determine if the server is opting-in to receiving a cross-origin POST
that has Authorization
and Content-Type: application/json
headers.
It works pretty well with a small curl script - I get my data.
To properly test with curl
, you must emulate the preflight OPTIONS
the browser sends:
curl -i -X OPTIONS -H "Origin: http://127.0.0.1:3000" \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: Content-Type, Authorization' \
"https://the.sign_in.url"
…with https://the.sign_in.url
replaced by whatever your actual sign_in
URL is.
The response the browser needs from that OPTIONS
request must have headers like this:
Access-Control-Allow-Origin: http://127.0.0.1:3000
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization
If the OPTIONS
response doesn’t include those headers, the browser will stop right there and never attempt to send the POST
request. Also, the HTTP status code for the response must be a 2xx—typically 200 or 204. If it’s any other status code, the browser will stop right there.
The server in the question responds to the OPTIONS
request with a 501 status code, which apparently means it’s trying to indicate it doesn’t implement support for OPTIONS
requests. Other servers typically respond with a 405 “Method not allowed” status code in this case.
So you’ll never be able to make POST
requests directly to that server from your frontend JavaScript code if the server responds to that OPTIONS
request with a 405 or 501 or anything other than a 200 or 204 or if doesn’t respond with those necessary response headers.
The way to avoid triggering a preflight for the case in the question would be:
- if the server didn’t require an
Authorization
request header but instead, e.g., relied on authentication data embedded in the body of the POST
request or as a query param
- if the server didn’t require the
POST
body to have a Content-Type: application/json
media type but instead accepted the POST
body as application/x-www-form-urlencoded
with a parameter named json
(or whatever) whose value is the JSON data
How to fix “Access-Control-Allow-Origin header must not be the wildcard” problems
I am getting another error message:
The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is
'include'. Origin 'http://127.0.0.1:3000
' is therefore not allowed
access. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.
For requests that have credentials, browsers won’t let your frontend JavaScript code access the response if the value of the Access-Control-Allow-Origin
header is *
. Instead the value in that case must exactly match your frontend code’s origin, http://127.0.0.1:3000
.
See Credentialed requests and wildcards in the MDN HTTP access control (CORS) article.
If you control the server you’re sending the request to, a common way to deal with this case is to configure the server to take the value of the Origin
request header, and echo/reflect that back into the value of the Access-Control-Allow-Origin
response header; e.g., with nginx:
add_header Access-Control-Allow-Origin $http_origin
But that’s just an example; other (web) server systems have similar ways to echo origin values.
I am using Chrome. I also tried using that Chrome CORS Plugin
That Chrome CORS plugin apparently just simplemindedly injects an Access-Control-Allow-Origin: *
header into the response the browser sees. If the plugin were smarter, what it would be doing is setting the value of that fake Access-Control-Allow-Origin
response header to the actual origin of your frontend JavaScript code, http://127.0.0.1:3000
.
So avoid using that plugin, even for testing. It’s just a distraction. To test what responses you get from the server with no browser filtering them, you’re better off using curl -H
as above.
As far as the frontend JavaScript code for the fetch(…)
request in the question:
headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
headers.append('Access-Control-Allow-Credentials', 'true');
Remove the lines above. The Access-Control-Allow-*
headers are response headers. You never want to send them in requests. The only effect of that is to trigger a browser to execute a preflight.
Best Answer
This is by design. You can't make an arbitrary HTTP request to another server using XMLHttpRequest unless that server allows it by putting out an Access-Control-Allow-Origin header for the requesting host.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
You could retrieve it in a script tag (there isn't the same restriction on scripts and images and stylesheets), but unless the content returned is a script, it won't do you much good.
Here's a tutorial on CORS:
http://www.bennadel.com/blog/2327-cross-origin-resource-sharing-cors-ajax-requests-between-jquery-and-node-js.htm
This is all done to protect the end user. Assuming that an image is actually an image, a stylesheet is just a stylesheet and a script is just a script, requesting those resources from another server can't really do any harm.
But in general, cross-origin requests can do really bad things. Say that you, Zoltan, are using coolsharks.com. Say also that you are logged into mybank.com and there is a cookie for mybank.com in your browser. Now, suppose that coolsharks.com sends an AJAX request to mybank.com, asking to transfer all your money into another account. Because you have a mybank.com cookie stored, they successfully complete the request. And all of this happens without your knowledge, because no page reload occurred. This is the danger of allowing general cross-site AJAX requests.
If you want to perform cross-site requests, you have two options:
a. Admit you by putting out a Access-Control-Allow-Origin header that includes you (or *)
b. Provide you with a JSONP API.
or
In (1), you must have the cooperation of the server you are making requests to, and in (2), you must have control over the end user's browser. If you can't fulfill (1) or (2), you're pretty much out of luck.
However, there is a third option (pointed out by charlietfl). You can make the request from a server that you do control and then pass the result back to your page. E.g.
And then on your server, at its most simple:
Of course, this method may run into other issues:
I'm sure there are others. But if none of those issues prevent it, this third method could work quite well.