I run backend at http://localhost:4000
and frontend at http://localhost:3000
. In server (Express) I set cors
policy as:
app.use( cors({
credentials: true,
origin: "http://localhost:3000",
}));
But the cookie is not set. After the request
POST /graphql HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Content-Length: 373
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36
content-type: application/json
Accept: */*
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:3000/register
Accept-Encoding: gzip, deflate, br
Accept-Language: et-EE,et;q=0.9,en-US;q=0.8,en;q=0.7
I got back response headers with correct(?) Set-Cookie
:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Content-Length: 122
ETag: W/"7a-3NezKfolEhPugNDLsBCed9Xgfzk"
Set-Cookie: lirr=s%3A9JJT7_mt6W-zSWkg-DKkBmxK6O-i8bH3.1k88dMMeintjdSUMV6ig1DK5u4vgdYh4rnmtufww7nw; Path=/; Expires=Sat, 27 Nov 2021 09:31:14 GMT; HttpOnly; SameSite=Lax
Date: Fri, 27 Nov 2020 09:31:14 GMT
Connection: keep-alive
Keep-Alive: timeout=5
I tried with Chrome and Firefox and neither set the cookie. I don't see a flaw in my setup, headers seem fine. What am I missing?
IN addition
If I let set cookie from the backend (exposed as Graphql API, accessed with Graphql playground), it works fine with such req headers:
POST /graphql HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Content-Length: 323
accept: */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36
content-type: application/json
Origin: http://localhost:4000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:4000/graphql
Accept-Encoding: gzip, deflate, br
Accept-Language: et-EE,et;q=0.9,en-US;q=0.8,en;q=0.7
As far I notice, 3 lines (as expected) differ:
Origin: http://localhost:4000
Sec-Fetch-Site: same-origin
Referer: http://localhost:4000/graphql
A reponse is pretty same as frontend's:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Content-Length: 75
ETag: W/"4b-Ud8RJ2O7ZjwpTxFNSAQi92iV20w"
Set-Cookie: lirr=s%3A9CYQjFpVwG69-ojwqlk-FJWVNC02DMJP.Prt8U5%2Bcco9ktEL27JoOkAlACGKS%2Fy1NHfgugPypl00; Path=/; Expires=Sat, 27 Nov 2021 10:32:37 GMT; HttpOnly; SameSite=Lax
Date: Fri, 27 Nov 2020 10:32:37 GMT
Connection: keep-alive
Keep-Alive: timeout=5
I don't understand, why the browser does not set the cookie through the frontend...
Your setting looks fine to me.
app.use( cors({
credentials: true,
origin: "http://localhost:3000",
}));
I suggest testing your code with some API tool like POSTman which enables CORS request as default. If it works with POSTman, then your server side code has no problem.
Simply, CORS request is nothing but XMLHttpRequest to different domain. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
According to the following doc, https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
In addition, this flag is also used to indicate when cookies are to be ignored in the response. The default is false. XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request. The third-party cookies obtained by setting withCredentials to true will still honor same-origin policy and hence can not be accessed by the requesting script through document.cookie or from response headers.
Browser will not accept/transfer cookies via CORS request(XMLHttpRequest) unless you enable it.
xhr.withCredentials = true;
//from http://localhost:3000
const xhr = new XMLHttpRequest();
const url = 'http://localhost:4000/get-cookie-url';
xhr.open('GET', url);
xhr.withCredentials = true; // enable browser to transfer cookies
xhr.onload = function(e){
console.log(e)
};
xhr.send();
To illustrate, cookie transfer is similar to handshake.
Both side need to initiate for handshake. If one side(server) initiates and the other(client) does not, handshake will not happen.
When Server initiates cookie transfer,
//Server response header
//Server will response with "Set-Cookie" header when browser requests
...
Set-Cookie: lirr=xxxx; ...
...
When Client(Browser) initiates cookie transfer,
//Client request header
//Client will request with "Cookie" header
...
Cookie: lirr=xxxx; ....
...
Through Postman I got cookies set. Good idea, thank you! Next, your links gave me some hints to look for "credentials" client side. From github.com/benawad/how-to-debug-cookies I got idea how to add "withCredentials" to my client-side. And if I add, cookies are set as needed. BUT: i don't see any difference in headers, even not in preflight ones. So practical half is solved, but theoretical is still not clear to me.
@w.k , well done, it is great to hear you sorted some of it out. To illustrate, cookie transfer is very similar to handshake. I will update the above post with some details.
I think, I got it clear after reading more about CORS: I use
urql
'screateClient
function, which uses JSRequest()
for HTTP requests. AndRequest
has default 'same-origin' forcredentials
. If I not set it client-side to 'include', it just ignores cookies which does not have set by orginal origin. I assumed, that this info is included to the headers, but they have managed in client-side. Thank you once more, your hints lead me to solution.@w.k , awesome👍 , Thank you, your issue was good review for me as well. Since netscape time, web browser standard has been changed a lot. Also our tools/frameworks abstract underneath. Because of that, we got confused sometimes.🤣