nodejs / undici

An HTTP/1.1 client, written from scratch for Node.js
https://nodejs.github.io/undici
MIT License
6.26k stars 545 forks source link

Persist Cookies between redirects #3784

Open OZZlE opened 3 weeks ago

OZZlE commented 3 weeks ago

We have an Api that you need to call on endpoint A, it sets cookies and redirects to B (and you cannot call B directly) but undici looses the cookies so the Api call fails.

(Update: both A and B live on the same origins but diffent paths and the cookie path is set on /)

fetch(
        "https://example.com/api",
        {
            method: "POST",
            credentials: 'include', // this guy should probably make this assumption if I understood the spec correctly
            redirect: "follow", // together with him
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            },
            body: urlencoded.toString(),
        }
    )

I also considered:

import { fetch as uFetch } from 'undici';
import fetchCookie from 'fetch-cookie';
const fetch = fetchCookie(uFetch);

However on Win11 I want to use SSL certs which is poorly implemented in windows so I have to use this syntax:

const ca = (await import('win-ca')).default;
const rootCAs = []
// Fetch all certificates in PEM format
ca({
    format: ca.der2.pem,
    ondata: buf => rootCAs.push(buf.toString())
})

async function uWinCaFetch(url) {
    const dispatcher = new Agent({
        connect: {
            ca: rootCAs, // Pass all root certificates
        }
    });
    return request(url, { dispatcher });
}

But trying to wrap that with fetchCookie like: const fetch = fetchCookie(uWinCaFetch); only becomes:

file:///C:/ws/projects/my-card/node_modules/fetch-cookie/esm/index.js:129
const cookieString = response.headers.get("set-cookie");
                                        ^

TypeError: response.headers.get is not a function
    at getCookiesFromResponse (file:///C:/ws/projects/my-card/node_modules/fetch-cookie/esm/index.js:129:41)
    at fetchCookieWrapper (file:///C:/ws/projects/my-card/node_modules/fetch-cookie/esm/index.js:145:21)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async GET (file:///C:/ws/projects/my-card/test.mjs:62:9)
    at async file:///C:/ws/projects/my-card/test.mjs:92:1

Additional context

ronag commented 3 weeks ago

@KhafraDev

KhafraDev commented 3 weeks ago

If A and B are different origins, the spec tells us to remove cookies on cross-origin redirects.

OZZlE commented 3 weeks ago

If A and B are different origins, the spec tells us to remove cookies on cross-origin redirects.

Yep, in my case the hostname is identical, path should not matter unless the cookie was set on a specific path other than /, our cookies also use / as path

KhafraDev commented 3 weeks ago

"Same origin" in this case requires the protocol, hostname, and port to be identical. If this is true, I need a reproducible sample.

OZZlE commented 3 weeks ago

They are identical, our website is behind login. That using fetchCookie solves the issue should be proof enough that the issue exists. I will get back to you if I can find time to set up an example, it might take some time to implement.

Times like these node.js feels a bit imature compared to many of the older web backends.

KhafraDev commented 3 weeks ago

I may have been concentrating on your same origin comment too much since we've had similar reports in the past - undici does not implement a cookie jar.

import { once } from 'node:events'
import { createServer } from 'node:http'

const server = createServer((req, res) => {
  if (req.url === '/path1') {
    res.writeHead(302, undefined, {
      location: '/path2',
      'set-cookie': 'a=b'
    })
    res.end()
  } else {
    console.log('ok')
    console.log(req.headers)
    res.end()
  }
}).listen(0)

await once(server, 'listening')

const v = await fetch(`http://localhost:${server.address().port}/path1`)

console.log(v.headers.getSetCookie()) // empty