I am using Express.js server. With cookie-parser
I have opened this endpoint
app.get("/s", (req,res) => {
res.cookie("bsaSession", req.session.id)
res.send("set cookie ok")
})
When I manually use the browser to http://localhost:5555/s
where I have the website running the browser debug console shows that the cookie have been applied.
But when I use fetch
API to do the equivalent, it does not set the cookie.
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'same-origin'
})
}
Why?
If you set credentials to same-origin : Fetch will send 1st party cookies to its own server. It will not send cookies to other domains or subdomains. If you set credentials to include : Fetch will continue to send 1st party cookies to its own server.
Check out the OPTIONS response header ACCESS-CONTROL-ALLOW-CREDENTIAL whether it is set to true . If the server doesn't allow credentials being sent along, the browser will just not attach cookies and authorization headers. So this could be another reason why the cookies are missing in the POST cross-site request.
Unless fetch() is called with the credentials option set to include , fetch() : won't send cookies in cross-origin requests. won't set any cookies sent back in cross-origin responses. As of August 2018, the default credentials policy changed to same-origin.
The Set-Cookie HTTP response header is used to send a cookie from the server to the user agent, so that the user agent can send it back to the server later. To send multiple cookies, multiple Set-Cookie headers should be sent in the same response.
I have found the solution. The core of this problem being that my button to trigger the fetch
is on http://localhost:3000/
. The server is on http://localhost:5555/
(I am simulating real environment on my own machine)
The problem is that this fetch
call
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'same-origin'
})
}
Without credentials
, the browser cannot send or receive cookies via fetch
(https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
With credentials
as same-origin
I can see the cookies coming from the server in the Set-Cookie
response header, but nothing is being stored in the browser. One strange thing is that this response always have HttpOnly
tagged after the cookie string regardless of my {httpOnly : true/false}
settings on the server. In the case of manually using the browser to the page to do GET request, HttpOnly
is being respected as usual, and the cookies are set.
So the solution is to set credentials
as include
to allow cross-origin cookie sending.
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'include'
})
}
Also, on the server side you need to allow a particular origin manually with new headers:
app.get("/s", (req,res) => {
res.cookie("bsaSession", req.session.id, {httpOnly:false})
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
res.header('Access-Control-Allow-Credentials','true'
res.send("set")
})
Not doing this results in
XMLHttpRequest cannot load http://localhost:5555/s. Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
But the cookie will be set regardless of this error. Still nice to include that header to silence the error.
If you are using cors
middleware for Express
it is even easier. You can just use these options
var corsOptions = {
origin: 'http://localhost:3000',
credentials: true
}
app.use(cors(corsOptions))
And of course credentials: 'include'
is still required at the client side.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With