Warm tip: This article is reproduced from serverfault.com, please click

Why cookie in res headers is not set in browser?

发布于 2020-11-27 10:28:34

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...

Questioner
w.k
Viewed
0
John 2020-12-01 10:06:32

Server side check

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.

Browser issue with CORS + Cookie

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;

Example via browser console

//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();

Cookie transfer

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; ....
...