Javascript fetch is not sending Cookie Header (CORS) - javascript

I am trying to send Cookies to a PHP Script within a javascript fetch CORS request. The Request starts on https://sub1.example.com and contains the following options:
let response = await fetch('https://sub2.example.com/target.php', {
method: "POST",
headers: headers,
body: formData,
mode: 'cors',
credentials: 'include',
cache: 'no-store'
});
The corresponding PHP Script sets the following Headers:
header('Access-Control-Allow-Origin: https://www.example.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization, X-Request-With, Set-Cookie, Cookie, Bearer');
But the Cookie Header is not send with the request. I also tried:
let headers = new Headers();
headers.set('Cookie', document.cookie);
That also had no effect. What exactly am I doing wrong here?
I checked the Network Tab in the Development Tools. Also $_COOKIE in the PHP Script is empty. There is absolutely no error. I can also see that the Cookie Header is sent in any not CORS fetch request.
EDIT: Here are the Settings of one of the Cookies:
Name: PHPSESSID
Path: /
Secure: true
SameSite: none
I can't share the Domain because it's not public. But the Cookie Domain has the same Value as the Origin in the Request Header (Minus the https://).
EDIT 2: Changed the fetch URL to make clearer what's happening.

Problem
Be aware that, depending on
the value of the cookie's Path attribute,
the effective value of the cookie's Domain attribute,
the value of the cookie's Secure attribute,
the effective value of the cookie's SameSite attribute,
the request's issuing and destination origins,
a cookie may or may not be attached to the request. Of particular relevance to your case is the Domain attribute; check out MDN's page on the topic:
The Domain attribute specifies which hosts can receive a cookie. If unspecified, the attribute defaults to the same host that set the cookie, excluding subdomains. If Domain is specified, then subdomains are always included. Therefore, specifying Domain is less restrictive than omitting it. However, it can be helpful when subdomains need to share information about a user.
You're setting the cookie as follows on origin https://sub1.example.com:
Set-Cookie: PHPSESSID=whatever; Path=/; SameSite=None; Secure
Therefore, that cookie will get attached to (credentialed) requests whose destination origin is https://sub1.example.com, and no other.
Solution
If you want your cookie to be sent to all secure origins whose domain is an example.com subdomain, you need to explicitly set its Domain to example.com.
About sending cookies with fetch
The Fetch standard specifies a list of forbidden header names; Cookie is one of them. You cannot set a header named Cookie on a request sent with fetch; the standard simply forbids it. If you want to attach existing cookies to a cross-origin request, use the 'include' value for the credentials parameter passed in fetch options.

Cookies normally are not supposed to be attached to preflight requests in CORS mode. You might want to check this out.
Note: Browsers should not send credentials in preflight requests irrespective of this setting. For more information see: CORS > Requests with credentials.
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

These are the conditions that need to be met in order for the browser to save and then use cookies initiated using fetch:
Client initializes asynchronously a fetch request with credentials: 'include'. See [here][1] for more details.
To do CORS, server response header must contain Access-Control-Allow-Origin explicitly set to a domain, could be different from the server domain. For example, in a Single-Page-App architecture, your frontend site is temporarily hosted at localhost:3000 and your backend server hosted at localhost:8000, then the header should be Access-Control-Allow-Origin: http://localhost:3000. See [here][2] and [here][3].
To allow client to process cookies, which is obviously a sensitive resource, server response header must further contain Access-Control-Allow-Credentials: true. See [here][4]. Note that this enforces a non-wildcard setting for Access-Control-Allow-Origin. See [here][6] - that's why in point 2 above, it has to be explicitly set to something like http://localhost:3000 rather than *
When server sets the cookie, it has to include SameSite=None; Secure; HttpOnly. So overall something like Set-Cookie: session_id=12345; SameSite=None; Secure; HttpOnly. SameSite seems to be a relatively [new requirement][5] in latest browsers, and must be used with Secure together when SameSite is set to None.
With regard to HttpOnly, I haven't found relevant materials, but in my experiment, omitting it caused the browser to ignore the Set-Cookie header.
Further requests to the backend server also must have credentials: 'include' set.
Source: https://stackoverflow.com/a/67001424/368691

Related

I created an api in express, and a page in react to consume this api, but the page is not sending the cookie in headers [duplicate]

I am trying to send Cookies to a PHP Script within a javascript fetch CORS request. The Request starts on https://sub1.example.com and contains the following options:
let response = await fetch('https://sub2.example.com/target.php', {
method: "POST",
headers: headers,
body: formData,
mode: 'cors',
credentials: 'include',
cache: 'no-store'
});
The corresponding PHP Script sets the following Headers:
header('Access-Control-Allow-Origin: https://www.example.com');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization, X-Request-With, Set-Cookie, Cookie, Bearer');
But the Cookie Header is not send with the request. I also tried:
let headers = new Headers();
headers.set('Cookie', document.cookie);
That also had no effect. What exactly am I doing wrong here?
I checked the Network Tab in the Development Tools. Also $_COOKIE in the PHP Script is empty. There is absolutely no error. I can also see that the Cookie Header is sent in any not CORS fetch request.
EDIT: Here are the Settings of one of the Cookies:
Name: PHPSESSID
Path: /
Secure: true
SameSite: none
I can't share the Domain because it's not public. But the Cookie Domain has the same Value as the Origin in the Request Header (Minus the https://).
EDIT 2: Changed the fetch URL to make clearer what's happening.
Problem
Be aware that, depending on
the value of the cookie's Path attribute,
the effective value of the cookie's Domain attribute,
the value of the cookie's Secure attribute,
the effective value of the cookie's SameSite attribute,
the request's issuing and destination origins,
a cookie may or may not be attached to the request. Of particular relevance to your case is the Domain attribute; check out MDN's page on the topic:
The Domain attribute specifies which hosts can receive a cookie. If unspecified, the attribute defaults to the same host that set the cookie, excluding subdomains. If Domain is specified, then subdomains are always included. Therefore, specifying Domain is less restrictive than omitting it. However, it can be helpful when subdomains need to share information about a user.
You're setting the cookie as follows on origin https://sub1.example.com:
Set-Cookie: PHPSESSID=whatever; Path=/; SameSite=None; Secure
Therefore, that cookie will get attached to (credentialed) requests whose destination origin is https://sub1.example.com, and no other.
Solution
If you want your cookie to be sent to all secure origins whose domain is an example.com subdomain, you need to explicitly set its Domain to example.com.
About sending cookies with fetch
The Fetch standard specifies a list of forbidden header names; Cookie is one of them. You cannot set a header named Cookie on a request sent with fetch; the standard simply forbids it. If you want to attach existing cookies to a cross-origin request, use the 'include' value for the credentials parameter passed in fetch options.
Cookies normally are not supposed to be attached to preflight requests in CORS mode. You might want to check this out.
Note: Browsers should not send credentials in preflight requests irrespective of this setting. For more information see: CORS > Requests with credentials.
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
These are the conditions that need to be met in order for the browser to save and then use cookies initiated using fetch:
Client initializes asynchronously a fetch request with credentials: 'include'. See [here][1] for more details.
To do CORS, server response header must contain Access-Control-Allow-Origin explicitly set to a domain, could be different from the server domain. For example, in a Single-Page-App architecture, your frontend site is temporarily hosted at localhost:3000 and your backend server hosted at localhost:8000, then the header should be Access-Control-Allow-Origin: http://localhost:3000. See [here][2] and [here][3].
To allow client to process cookies, which is obviously a sensitive resource, server response header must further contain Access-Control-Allow-Credentials: true. See [here][4]. Note that this enforces a non-wildcard setting for Access-Control-Allow-Origin. See [here][6] - that's why in point 2 above, it has to be explicitly set to something like http://localhost:3000 rather than *
When server sets the cookie, it has to include SameSite=None; Secure; HttpOnly. So overall something like Set-Cookie: session_id=12345; SameSite=None; Secure; HttpOnly. SameSite seems to be a relatively [new requirement][5] in latest browsers, and must be used with Secure together when SameSite is set to None.
With regard to HttpOnly, I haven't found relevant materials, but in my experiment, omitting it caused the browser to ignore the Set-Cookie header.
Further requests to the backend server also must have credentials: 'include' set.
Source: https://stackoverflow.com/a/67001424/368691

HttpOnly Cookie and fetch

Will a cookie with the HttpOnly and Secure attributes be sent using Fetch API in case {credentials: "include"} is present in options?
fetch("https://some.url", {
mode: "same-origin",
credentials: "include",
redirect: "manual"
})
There are several conditions that have to be met, but yes they are.
Client initializes asynchronously a fetch request with credentials: 'include'. See [here][1] for more details.
To do CORS, server response header must contain Access-Control-Allow-Origin explicitly set to a domain, could be different from the server domain. For example, in a Single-Page-App architecture, your frontend site is temporarily hosted at localhost:3000 and your backend server hosted at localhost:8000, then the header should be Access-Control-Allow-Origin: http://localhost:3000. See [here][2] and [here][3].
To allow client to process cookies, which is obviously a sensitive resource, server response header must further contain Access-Control-Allow-Credentials: true. See [here][4]. Note that this enforces a non-wildcard setting for Access-Control-Allow-Origin. See [here][6] - that's why in point 2 above, it has to be explicitly set to something like http://localhost:3000 rather than *
When server sets the cookie, it has to include SameSite=None; Secure; HttpOnly. So overall something like Set-Cookie: session_id=12345; SameSite=None; Secure; HttpOnly. SameSite seems to be a relatively [new requirement][5] in latest browsers, and must be used with Secure together when SameSite is set to None.
With regard to HttpOnly, I haven't found relevant materials, but in my experiment, omitting it caused the browser to ignore the Set-Cookie header.
Further requests to the backend server also must have credentials: 'include' set.
Source: https://stackoverflow.com/a/67001424/368691

CORS Cookie not set on cross domains, using fetch, set credentials: 'include' and origins have been set

I'm using fetch to do a request to the backend.
The cookie ISN'T set when I use a different domain.
The cookie IS set when I use the same domain.
Why is it not being set?
I modified my /etc/hosts file to use pseudonymns to test using the same and different domain, and made sure they are not blacklisted by the browser either.
If I use local-test-frontend.com for both the browser and server domain it works, but if I change the backend url to local-test-backend.com it fails.
*Note that my front end url I test it from is * http://local-test-frontend.com:3000/login
Javascript
fetch('http://local-test-backend.com/login', {
mode: 'cors',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(loginRequest),
credentials: 'include'
}).then(// Other code here.....
Server Response Headers
Access-Control-Allow-Credentials
true
Access-Control-Allow-Origin
http://local-test-frontend.com:3000
Content-Length
103
Content-Type
application/json
Date
Wed, 10 Jul 2019 07:23:49 GMT
Server
Werkzeug/0.15.1 Python/3.7.3
Set-Cookie
MY_TOKEN=a7b8ad50f19…end.com; Path=/; SameSite=Lax
As of 2021 with Edge 90.0.796.0 on Linux, I managed to set CORS cookie with the following approach:
Client initializes asynchronously a fetch request with credentials: 'include'. See here for more details.
To do CORS, server response header must contain Access-Control-Allow-Origin explicitly set to a domain, could be different from the server domain. For example, in a Single-Page-App architecture, your frontend site is temporarily hosted at localhost:3000 and your backend server hosted at localhost:8000, then the header should be Access-Control-Allow-Origin: http://localhost:3000. See here and here.
To allow client to process cookies, which is obviously a sensitive resource, server response header must further contain Access-Control-Allow-Credentials: true. See here. Note that this enforces a non-wildcard setting for Access-Control-Allow-Origin. See here - that's why in point 2 above, it has to be explicitly set to something like http://localhost:3000 rather than *
When server sets the cookie, it has to include SameSite=None; Secure; HttpOnly. So overall something like Set-Cookie: session_id=12345; SameSite=None; Secure; HttpOnly. SameSite seems to be a relatively new requirement in latest browsers, and must be used with Secure together when SameSite is set to None.
With regard to HttpOnly, I haven't found relevant materials, but in my experiment, omitting it caused the browser to ignore the Set-Cookie header.
Further requests to the backend server also must have credentials: 'include' set.
I'm just trying to get a cookie set for my current domain by calling a server on a different domain.
You can't, at least not directly. Cookies belong to the origin that set them.
The closest you could come would be for the different domain to return the data in a non-Cookie format (such as the body of the response), and then to use client-side JS to store it using document.cookie.

Fetch API: No 'Access-Control-Allow-Origin' error even after adding "Access-Control-Allow-Origin": "*" to my headers [duplicate]

Apparently, I have completely misunderstood its semantics. I thought of something like this:
A client downloads JavaScript code MyCode.js from http://siteA - the origin.
The response header of MyCode.js contains Access-Control-Allow-Origin: http://siteB, which I thought meant that MyCode.js was allowed to make cross-origin references to the site B.
The client triggers some functionality of MyCode.js, which in turn make requests to http://siteB, which should be fine, despite being cross-origin requests.
Well, I am wrong. It does not work like this at all. So, I have read Cross-origin resource sharing and attempted to read Cross-Origin Resource Sharing in w3c recommendation.
One thing is sure - I still do not understand how I am supposed to use this header.
I have full control of both site A and site B. How do I enable the JavaScript code downloaded from the site A to access resources on the site B using this header?
P.S.: I do not want to utilize JSONP.
Access-Control-Allow-Origin is a CORS (cross-origin resource sharing) header.
When Site A tries to fetch content from Site B, Site B can send an Access-Control-Allow-Origin response header to tell the browser that the content of this page is accessible to certain origins. (An origin is a domain, plus a scheme and port number.) By default, Site B's pages are not accessible to any other origin; using the Access-Control-Allow-Origin header opens a door for cross-origin access by specific requesting origins.
For each resource/page that Site B wants to make accessible to Site A, Site B should serve its pages with the response header:
Access-Control-Allow-Origin: http://siteA.com
Modern browsers will not block cross-domain requests outright. If Site A requests a page from Site B, the browser will actually fetch the requested page on the network level and check if the response headers list Site A as a permitted requester domain. If Site B has not indicated that Site A is allowed to access this page, the browser will trigger the XMLHttpRequest's error event and deny the response data to the requesting JavaScript code.
Non-simple requests
What happens on the network level can be slightly more complex than explained above. If the request is a "non-simple" request, the browser first sends a data-less "preflight" OPTIONS request, to verify that the server will accept the request. A request is non-simple when either (or both):
using an HTTP verb other than GET or POST (e.g. PUT, DELETE)
using non-simple request headers; the only simple requests headers are:
Accept
Accept-Language
Content-Language
Content-Type (this is only simple when its value is application/x-www-form-urlencoded, multipart/form-data, or text/plain)
If the server responds to the OPTIONS preflight with appropriate response headers (Access-Control-Allow-Headers for non-simple headers, Access-Control-Allow-Methods for non-simple verbs) that match the non-simple verb and/or non-simple headers, then the browser sends the actual request.
Supposing that Site A wants to send a PUT request for /somePage, with a non-simple Content-Type value of application/json, the browser would first send a preflight request:
OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
Note that Access-Control-Request-Method and Access-Control-Request-Headers are added by the browser automatically; you do not need to add them. This OPTIONS preflight gets the successful response headers:
Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
When sending the actual request (after preflight is done), the behavior is identical to how a simple request is handled. In other words, a non-simple request whose preflight is successful is treated the same as a simple request (i.e., the server must still send Access-Control-Allow-Origin again for the actual response).
The browsers sends the actual request:
PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json
{ "myRequestContent": "JSON is so great" }
And the server sends back an Access-Control-Allow-Origin, just as it would for a simple request:
Access-Control-Allow-Origin: http://siteA.com
See Understanding XMLHttpRequest over CORS for a little more information about non-simple requests.
Cross-Origin Resource Sharing - CORS (A.K.A. Cross-Domain AJAX request) is an issue that most web developers might encounter, according to Same-Origin-Policy, browsers restrict client JavaScript in a security sandbox, usually JS cannot directly communicate with a remote server from a different domain. In the past developers created many tricky ways to achieve Cross-Domain resource request, most commonly using ways are:
Use Flash/Silverlight or server side as a "proxy" to communicate
with remote.
JSON With Padding (JSONP).
Embeds remote server in an iframe and communicate through fragment or window.name, refer here.
Those tricky ways have more or less some issues, for example JSONP might result in security hole if developers simply "eval" it, and #3 above, although it works, both domains should build strict contract between each other, it neither flexible nor elegant IMHO:)
W3C had introduced Cross-Origin Resource Sharing (CORS) as a standard solution to provide a safe, flexible and a recommended standard way to solve this issue.
The Mechanism
From a high level we can simply deem CORS as a contract between client AJAX call from domain A and a page hosted on domain B, a typical Cross-Origin request/response would be:
DomainA AJAX request headers
Host DomainB.com
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
Accept-Language en-us;
Accept-Encoding gzip, deflate
Keep-Alive 115
Origin http://DomainA.com
DomainB response headers
Cache-Control private
Content-Type application/json; charset=utf-8
Access-Control-Allow-Origin DomainA.com
Content-Length 87
Proxy-Connection Keep-Alive
Connection Keep-Alive
The blue parts I marked above were the kernal facts, "Origin" request header "indicates where the cross-origin request or preflight request originates from", the "Access-Control-Allow-Origin" response header indicates this page allows remote request from DomainA (if the value is * indicate allows remote requests from any domain).
As I mentioned above, W3 recommended browser to implement a "preflight request" before submiting the actually Cross-Origin HTTP request, in a nutshell it is an HTTP OPTIONS request:
OPTIONS DomainB.com/foo.aspx HTTP/1.1
If foo.aspx supports OPTIONS HTTP verb, it might return response like below:
HTTP/1.1 200 OK
Date: Wed, 01 Mar 2011 15:38:19 GMT
Access-Control-Allow-Origin: http://DomainA.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Max-Age: 1728000
Connection: Keep-Alive
Content-Type: application/json
Only if the response contains "Access-Control-Allow-Origin" AND its value is "*" or contain the domain who submitted the CORS request, by satisfying this mandtory condition browser will submit the actual Cross-Domain request, and cache the result in "Preflight-Result-Cache".
I blogged about CORS three years ago: AJAX Cross-Origin HTTP request
According to this Mozilla Developer Network article,
A resource makes a cross-origin HTTP request when it requests a resource from a different domain, or port than the one which the first resource itself serves.
An HTML page served from http://domain-a.com makes an <img> src request for http://domain-b.com/image.jpg.
Many pages on the web today load resources like CSS style sheets, images and scripts from separate domains (thus it should be cool).
Same-Origin Policy
For security reasons, browsers restrict cross-origin HTTP requests initiated from within scripts.
For example, XMLHttpRequest and Fetch follow the same-origin policy.
So, a web application using XMLHttpRequest or Fetch could only make HTTP requests to its own domain.
Cross-Origin Resource Sharing (CORS)
To improve web applications, developers asked browser vendors to allow cross-domain requests.
The Cross-origin resource sharing (CORS) mechanism gives web servers cross-domain access controls, which enable secure cross-domain data transfers.
Modern browsers use CORS in an API container - such as XMLHttpRequest or fetch - to mitigate risks of cross-origin HTTP requests.
How CORS works (Access-Control-Allow-Origin header)
Wikipedia:
The CORS standard describes new HTTP headers which provide browsers and servers a way to request remote URLs only when they have permission.
Although some validation and authorization can be performed by the server, it is generally the browser's responsibility to support these headers and honor the restrictions they impose.
Example
The browser sends the OPTIONS request with an Origin HTTP header.
The value of this header is the domain that served the parent page. When a page from http://www.example.com attempts to access a user's data in service.example.com, the following request header would be sent to service.example.com:
Origin: http://www.example.com
The server at service.example.com may respond with:
An Access-Control-Allow-Origin (ACAO) header in its response indicating which origin sites are allowed.
For example:
Access-Control-Allow-Origin: http://www.example.com
An error page if the server does not allow the cross-origin request
An Access-Control-Allow-Origin (ACAO) header with a wildcard that allows all domains:
Access-Control-Allow-Origin: *
Whenever I start thinking about CORS, my intuition about which site hosts the headers is incorrect, just as you described in your question. For me, it helps to think about the purpose of the same-origin policy.
The purpose of the same-origin policy is to protect you from malicious JavaScript on siteA.com accessing private information you've chosen to share only with siteB.com. Without the same-origin policy, JavaScript written by the authors of siteA.com could have your browser make requests to siteB.com, using your authentication cookies for siteB.com. In this way, siteA.com could steal the secret information you share with siteB.com.
Sometimes you need to work cross domain, which is where CORS comes in. CORS relaxes the same-origin policy for siteB.com, using the Access-Control-Allow-Origin header to list other domains (siteA.com) that are trusted to run JavaScript that can interact with siteB.com.
To understand which domain should serve the CORS headers, consider this. You visit malicious.com, which contains some JavaScript that tries to make a cross domain request to mybank.com. It should be up to mybank.com, not malicious.com, to decide whether or not it sets CORS headers that relax the same-origin policy, allowing the JavaScript from malicious.com to interact with it. If malicous.com could set its own CORS headers allowing its own JavaScript access to mybank.com, this would completely nullify the same-origin policy.
I think the reason for my bad intuition is the point of view I have when developing a site. It's my site, with all my JavaScript. Therefore, it isn't doing anything malicious, and it should be up to me to specify which other sites my JavaScript can interact with. When in fact I should be thinking: Which other sites' JavaScript are trying to interact with my site and should I use CORS to allow them?
From my own experience, it's hard to find a simple explanation why CORS is even a concern.
Once you understand why it's there, the headers and discussion becomes a lot clearer. I'll give it a shot in a few lines.
It's all about cookies. Cookies are stored on a client by their domain.
An example story: On your computer, there's a cookie for yourbank.com. Maybe your session is in there.
Key point: When a client makes a request to the server, it will send the cookies stored under the domain for that request.
You're logged in on your browser to yourbank.com. You request to see all your accounts, and cookies are sent for yourbank.com. yourbank.com receives the pile of cookies and sends back its response (your accounts).
If another client makes a cross origin request to a server, those cookies are sent along, just as before. Ruh roh.
You browse to malicious.com. Malicious makes a bunch of requests to different banks, including yourbank.com.
Since the cookies are validated as expected, the server will authorize the response.
Those cookies get gathered up and sent along - and now, malicious.com has a response from yourbank.
Yikes.
So now, a few questions and answers become apparent:
"Why don't we just block the browser from doing that?" Yep. That's CORS.
"How do we get around it?" Have the server tell the request that CORS is OK.
1. A client downloads javascript code MyCode.js from http://siteA - the origin.
The code that does the downloading - your html script tag or xhr from javascript or whatever - came from, let's say, http://siteZ. And, when the browser requests MyCode.js, it sends an Origin: header saying "Origin: http://siteZ", because it can see that you're requesting to siteA and siteZ != siteA. (You cannot stop or interfere with this.)
2. The response header of MyCode.js contains Access-Control-Allow-Origin: http://siteB, which I thought meant that MyCode.js was allowed to make cross-origin references to the site B.
no. It means, Only siteB is allowed to do this request. So your request for MyCode.js from siteZ gets an error instead, and the browser typically gives you nothing. But if you make your server return A-C-A-O: siteZ instead, you'll get MyCode.js . Or if it sends '*', that'll work, that'll let everybody in. Or if the server always sends the string from the Origin: header... but... for security, if you're afraid of hackers, your server should only allow origins on a shortlist, that are allowed to make those requests.
Then, MyCode.js comes from siteA. When it makes requests to siteB, they are all cross-origin, the browser sends Origin: siteA, and siteB has to take the siteA, recognize it's on the short list of allowed requesters, and send back A-C-A-O: siteA. Only then will the browser let your script get the result of those requests.
Using React and Axios, join a proxy link to the URL and add a header as shown below:
https://cors-anywhere.herokuapp.com/ + Your API URL
Just adding the proxy link will work, but it can also throw an error for No Access again. Hence it is better to add a header as shown below.
axios.get(`https://cors-anywhere.herokuapp.com/[YOUR_API_URL]`,{headers: {'Access-Control-Allow-Origin': '*'}})
.then(response => console.log(response:data);
}
Warning: Not to be used in production
This is just a quick fix. If you're struggling with why you're not able to get a response, you can use this.
But again it's not the best answer for production.
If you are using PHP, try adding the following code at the beginning of the php file:
If you are using localhost, try this:
header("Access-Control-Allow-Origin: *");
If you are using external domains such as server, try this:
header("Access-Control-Allow-Origin: http://www.website.com");
I worked with Express.js 4, Node.js 7.4 and Angular, and I had the same problem. This helped me:
a) server side: in file app.js I add headers to all responses, like:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
This must be before all routes.
I saw a lot of added this headers:
res.header("Access-Control-Allow-Headers","*");
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
But I don’t need that,
b) client side: in sending by Ajax, you need to add "withCredentials: true," like:
$http({
method: 'POST',
url: 'url',
withCredentials: true,
data : {}
}).then(function(response){
// Code
}, function (response) {
// Code
});
If you want just to test a cross-domain application in which the browser blocks your request, then you can just open your browser in unsafe mode and test your application without changing your code and without making your code unsafe.
From macOS, you can do this from the terminal line:
open -a Google\ Chrome --args --disable-web-security --user-data-dir
In Python, I have been using the Flask-CORS library with great success. It makes dealing with CORS super easy and painless. I added some code from the library's documentation below.
Installing:
pip install -U flask-cors
Simple example that allows CORS for all domains on all routes:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
#app.route("/")
def helloWorld():
return "Hello, cross-origin-world!"
For more specific examples, see the documentation. I have used the simple example above to get around the CORS issue in an Ionic application I am building that has to access a separate flask server.
Simply paste the following code in your web.config file.
Noted that, you have to paste the following code under <system.webServer> tag
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>
I can't configure it on the back-end server, but with these extensions in the browsers, it works for me:
For Firefox:
CORS Everywhere
For Google Chrome:
Allow CORS: Access-Control-Allow-Origin
Note: CORS works for me with this configuration:
For cross origin sharing, set header: 'Access-Control-Allow-Origin':'*';
Php: header('Access-Control-Allow-Origin':'*');
Node: app.use('Access-Control-Allow-Origin':'*');
This will allow to share content for different domain.
Nginx and Apache
As an addition to apsiller's answer, I would like to add a wiki graph which shows when a request is simple or not (and OPTIONS pre-flight request is send or not)
For a simple request (e.g., hotlinking images), you don't need to change your server configuration files, but you can add headers in the application (hosted on the server, e.g., in PHP) like Melvin Guerrero mentions in his answer - but remember: if you add full CORS headers in your server (configuration) and at same time you allow simple CORS in the application (e.g., PHP), this will not work at all.
And here are configurations for two popular servers:
turn on CORS on Nginx (nginx.conf file)
location ~ ^/index\.php(/|$) {
...
add_header 'Access-Control-Allow-Origin' "$http_origin" always; # if you change "$http_origin" to "*" you shoud get same result - allow all domain to CORS (but better change it to your particular domain)
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' "$http_origin"; # DO NOT remove THIS LINES (doubled with outside 'if' above)
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # arbitrary methods
add_header 'Access-Control-Allow-Headers' 'My-First-Header,My-Second-Header,Authorization,Content-Type,Accept,Origin'; # arbitrary headers
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
}
turn on CORS on Apache (.htaccess file)
# ------------------------------------------------------------------------------
# | Cross-domain Ajax requests |
# ------------------------------------------------------------------------------
# Enable cross-origin Ajax requests.
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
# http://enable-cors.org/
# change * (allow any domain) below to your domain
Header set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Allow-Headers "My-First-Header,My-Second-Header,Authorization, content-type, csrf-token"
Header always set Access-Control-Allow-Credentials "true"
The Access-Control-Allow-Origin response header indicates whether the
response can be shared with requesting code from the given origin.
Header type Response header
-------------------------------------------
Forbidden header name no
A response that tells the browser to allow code from any origin to
access a resource will include the following:
Access-Control-Allow-Origin: *
For more information, visit Access-Control-Allow-Origin...
For .NET Core 3.1 API With Angular
Startup.cs : Add CORS
//SERVICES
public void ConfigureServices(IServiceCollection services){
//CORS (Cross Origin Resource Sharing)
//=====================================
services.AddCors();
}
//MIDDLEWARES
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
//ORDER: CORS -> Authentication -> Authorization)
//CORS (Cross Origin Resource Sharing)
//=====================================
app.UseCors(x=>x.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200"));
app.UseHttpsRedirection();
}
}
Controller : Enable CORS For Authorized Controller
//Authorize all methods inside this controller
[Authorize]
[EnableCors()]
public class UsersController : ControllerBase
{
//ActionMethods
}
Note: Only a temporary solution for testing
For those who can't control the backend for Options 405 Method Not Allowed, here is a workaround for theChrome browser.
Execute in the command line:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="path_to_profile"
Example:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="C:\Users\vital\AppData\Local\Google\Chrome\User Data\Profile 2"
Most CORS issues are because you are trying to request via client side ajax from a react, angular, jquery apps that are frontend basic libs.
You must request from a backend application.
You are trying to request from a frontend API, but the API you are trying to consume is expecting this request to be made from a backend application and it will never accept client side requests.

Cookies are not accessible within JavaScript (and the dev tools) but sent along with XHR request (no httponly used)

I'm using both a front-end and a back-end application on a different domain with a session-based authorization. I have setup a working CORS configuration, which works as expected on localhost (e.g. from port :9000 to port :8080). As soon as I deploy the applications on secure domains (both domains only allow HTTPS), the CSRF cookie is not accessible anymore within JavaScript, leading to an incorrect follow-up request of the front-end (missing the CSRF header).
The cookie is set by the back-end in the Set-Cookie header without using the HttpOnly flag. It is actually set somewhere in the browser, because the follow-up request contains both the session cookie and the CSRF cookie. Trying to access it by JavaScript (using e.g. document.cookie in the console) returns an empty string. The DevTools of Chrome do not show any cookies on the front-end domain (the back-end domain is not even listed).
I'm expecting the cookie to be set and being visible on the current domain (front-end domain). I'm using the withCredentials flag of the axios library.
Do you have any idea, why the cookie cannot be accessed from JavaScript nor from the DevTools in Chrome? Does this have anything to do with the Strict-Transport-Security header?
Headers
1. Initial GET Response Header
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://[my-frontend-domain]
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Type: application/json;charset=UTF-8
Date: Wed, 20 Sep 2017 11:57:07 GMT
Expires: 0
Pragma: no-cache
Server: Apache-Coyote/1.1
Set-Cookie: CSRF-TOKEN=[some-token]; Path=/
Vary: Origin,Accept-Encoding
X-Content-Type-Options: nosniff
X-Vcap-Request-Id: [some-token]
X-Xss-Protection: 1; mode=block
Content-Length: [some-length]
Strict-Transport-Security: max-age=15768000; includeSubDomains
2. Follow-up POST Request Header
POST /api/authentication HTTP/1.1
Host: [my-backend-host]
Connection: keep-alive
Content-Length: [some-length]
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: [my-frontend-host]
User-Agent: [Google-Chrome-User-Agent]
Content-Type: application/x-www-form-urlencoded
DNT: 1
Referer: [my-frontend-host]
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,de-CH;q=0.2,it;q=0.2
Cookie: [some-other-cookies]; CSRF-TOKEN=[same-token-as-in-the-previous-request]
This request should contain a CSRF header which would automatically be added if the cookie was accessible with JavaScript.
TL;DR: Read-access to cross-domain cookies is not possible. Adding the CSRF token to the response header would be a solution. Another solution to completely circumvent CORS & cross-domain requests would be to use a reverse proxy.
Problem
As stated in my question above, the JavaScript part of my front-end (e.g. https://example1.com is trying to access a non-HttpOnly cookie from my back-end on e.g. https://example2.com. To be able to access a remote API with JavaScript, I'm using CORS. This allows the requests to go through. I'm using withCredentials: true on the front-end side and Access-Control-Allow-Credentials: true on the back-end side. The Set-Cookie header then sets the cookie on the back-end origin and not on the front-end origin. Therefore, the cookie is neither visible in the DevTools nor in the document.cookie command in JavaScript.
Cookies, set on the back-end origin, are always part of a request to the back-end via CORS. I would, however, need access to the content of the CSRF cookie to add the token into the request header (to prevent CSRF attacks). As I found out, there is no way to read (or write) cookies from a different domain with JavaScript – no matter what CORS setting is used (see these StackOverflow answers: [1], [2]). The browser restricts access to the content of a cookie to same-domain origins.
Solutions
This leads to the conclusion, that there is no possibility to access the contents of a non-HttpOnly cookie of a different domain. A workaround for this issue would be to set the CSRF token into an additional, custom response header. Those headers can usually also not be accessed by a different domain. They can however be exposed by the back-end's CORS setting Access-Control-Expose-Headers. This is secure, as long as one uses a strictly limited Access-Control-Allow-Origin header.
Another workaround would be to use a reverse proxy, which circumvents the issues with CORS and cross-domain requests at all. Using such a reverse proxy provides a special path on the front-end, which will be redirected to the back-end (server-side). For example, calls to https://front-end/api are proxied to https://back-end/api. Because all requests from the front-end are made to the front-end proxy on the same domain, the browser treats every call as a same-domain request and cookies are directly set on the front-end origin. Drawbacks of this solution comprise potential performance issues because of another server being in-between (delays) and the cookies need to be set on two origins (login twice when directly accessing the back-end). Setting up a reverse proxy can be done with nginx, apache or also very easy by using http-proxy-middleware in Node.js:
var express = require('express');
var proxy = require('http-proxy-middleware');
var options = {
target: 'https://[server]',
changeOrigin: true,
secure: true
};
var exampleProxy = proxy(options);
var app = express();
app.use('/api', exampleProxy);
app.use(express.static(__dirname + "/public"));
app.listen(process.env.PORT || 8080);
In short, it is not possible to access cross-origin cookies, document.cookie can only access the current (or parent) domain cookies.
The hint for that being the root cause, was ssc-hrep3 mentioning "both domains" in his question.
It's very to easy to make that mistake when switching from a localhost deployment, using only different ports for back-end and front-end servers, to one that uses two different hosts. That will work locally, because cookies are shared across ports, and will fail when two different hosts are used. (Unlike some other CORS issues that will be also exposed locally)
See ssc-hrep3's answer for more information and a workaround.
1
You may need to add Access-Control-Allow-Headers header to allow passing of specific headers.
Please try to add following into your server response headers (OPTIONS method) for testing purposes
Access-Control-Allow-Headers: Content-Type, *
In production I recomend to limit headers as following (but I'm not 100% sure in correct header list, need to experiment here if it works)
Access-Control-Allow-Headers: Cookie, Set-Cookie
See this for the reference https://quickleft.com/blog/cookies-with-my-cors/
2
Another problem that you may experince is that you cookies will be set on that domain where your backend service located (not on the domain you querying from)
Please check this also
3
As an option of last problem - browser can prohibit setting cookie for domain b.xxx.com from request which comes from a.xxx.com
In this case you may try to set cookie on the parent domain xxx.com, so it will be available for your client side
As you can read here, the XHR specification explictily disallows reading Set-Cookie. The best way to do it would be to pass information in a header instead of a cookie.

Categories

Resources