Node.jsのFetch APIでは、Cookieでログイン管理しているWebサイトを利用できない

マジかよ……
Node.jsでちょっとしたスクリプト書こうとして絶対ハマる人が出てくると思うので備忘録を書いていく

# 先に結論

理由は2つ

## 理由1、Fetch APIはそもそも`res.headers.getSetCookie`で値を返さない

https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie

> クライアントで次のようなコードを実行しても結果は返されません。— Set-Cookie は、ネットワーク経由で取得されたヘッダーからフィルタリングされます。

> ```js
> fetch("https://example.com").then((response) => {
>   console.log(response.headers.getSetCookie());
>   // No header values returned
> });
> ```

ネットワーク経由で取得されたヘッダー
つまりWebサーバが書き込んでくれたCookie情報をフィルタリングして取り除く仕様になっている

推測だけどクロスサイトスクリプティング等で
悪意のJavaScriptコードからCookieのセッション情報を抜かれて、
不正アクセスからのログイン情報乗っ取りという二次被害が出ない配慮だと思う。

## 理由2、Node.jsのfetch APIがCookieに対応してない

https://github.com/nodejs/node/issues/50742

> 質問者:
> Node.js v20.9.0 fetch API can't get set-cookie header set by cookie-parser

質問者「新しめのNode.jsでFetch APIを使ってる最中、Cookieの値が欲しくて`res.headers.getCookieSet()`を実行したのに値が取れないんだけど？」

> 回答者:
> I believe the current behavior of fetch is correct, since undici/fetch does not provide a cookies store. It is impossible to provide the cookies for redirection, unless the user implicitly provide it on the first request.

回答者「undici/fetch は Cookie ストアを提供していないため、fetch の現在の動作は正しいと思います。ユーザーが最初のリクエストで暗黙的に Cookie を提供しない限り、リダイレクト用の Cookie を提供することは不可能です。」

# 調査ログ

Node.jsでCookieを使ったログインシステムにアクセスする要望が出てきた
Fetch APIのリクエストを飛ばす時にCookieを追加するやり方は腐るほど出てくるが、
Fetch APIを使った時にWebサーバーにセットして貰ったCookieを抜き出す方法がググっても全然出てこない

こっちはログイン情報を残したいんだよ！

「fetch api get cookie」で検索するとMDNの公式サイトがヒット
https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie

MDNしか勝たん

でもこのHeadersとは何ぞや？

## Headers について調べる

Fetch APIのリクエストとレスポンスの両方で使うクラス
ビルトインで提供されているので、Node.js下でもrequireやimportしなくても使える

リクエストとして使う場合

```js
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");

const response = await fetch("https://example.org/post", {
  method: "POST",
  body: JSON.stringify({ username: "example" }),
  headers: myHeaders,
});
```

レスポンスで使う場合は`res.headers`で取得することが可能

```js
const res = await fetch("https://example.com");
console.log(response.headers.getSetCookie());
// setされたcookieは取得出来ないが、空配列の[]は受け取れる
```

参考URL:
- https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#making_post_requests
- https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie#examples

## `res.headers.getSetCookie`の落とし穴

最初の落とし穴

Fetch APIは元々ブラウザの中で実行されるAPI
つまり悪意の第三者のフィッシングサイトからリバースプロキシ的にログイン処理を行い
Cookie情報を抜かれるみたいな事をされかれないので、セキュリティが不自然に固くなっているわけだ

なので`Headers.getSetCookie()`のページをよくよく覗いてみると
それらしき記述がちらほら

https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie

冒頭をざっと和訳

> Headers インターフェイスの getSetCookie()メソッドは、レスポンスに関連付けられたすべての Set-Cookie ヘッダーの値を含む配列を返します。これにより、Headers オブジェクトは、実装前には不可能だった複数の Set-Cookie ヘッダーを持つことを処理できるようになります。

> このメソッドは、サーバー環境（Node。js など）での使用を目的としています。ブラウザは、Fetch 仕様で要求されているように、フロントエンド JavaScript コードが Set-Cookie ヘッダーにアクセスすることをブロックします。Fetch 仕様では、Set-Cookie を、フロントエンド コードに公開されているレスポンスからフィルタリングして除外する必要がある禁止されたレスポンス ヘッダー名として定義しています。

これらは一見Node.jsでも`Headers.getSetCookie()`で抜き出せるよと読み取れるが
このNode.jsというのはExpress等で立ち上げたWebサーバの話であり、
私の要望であるFetch APIで使う事は想像していないと思われる

更にExampleに入ってみる

> 上でほのめかしたように、クライアントで次のようなコードを実行しても結果は返されません。— Set-Cookie は、ネットワーク経由で取得されたヘッダーからフィルタリングされます。

> ```js
> fetch("https://example.com").then((response) => {
>   console.log(response.headers.getSetCookie());
>   // No header values returned
> });
> ```

> ただし、以下を使用して複数の Set-Cookie 値をクエリできます。これはサーバーでははるかに便利ですが、クライアントでも機能します。

> ```js
> const headers = new Headers({
>   "Set-Cookie": "name1=value1",
> });
> 
> headers.append("Set-Cookie", "name2=value2");
> 
> headers.getSetCookie();
> // Returns ["name1=value1", "name2=value2"]
> ```

取れないんじゃん！

最近SPAとかでCookieでアクセスするWebサイト少ないと思ったよ
こういう用途には使えないから最初からやらないわけだ

## スタフロでトドメ

https://github.com/nodejs/node/issues/50742

> 質問者:
> Node.js v20.9.0 fetch API can't get set-cookie header set by cookie-parser

質問者「新しめのNode.jsでFetch APIを使ってる最中、Cookieの値が欲しくて`res.headers.getCookieSet()`を実行したのに値が取れないんだけど？」

> 回答者:
> I believe the current behavior of fetch is correct, since undici/fetch does not provide a cookies store. It is impossible to provide the cookies for redirection, unless the user implicitly provide it on the first request.

回答者「undici/fetch は Cookie ストアを提供していないため、fetch の現在の動作は正しいと思います。ユーザーが最初のリクエストで暗黙的に Cookie を提供しない限り、リダイレクト用の Cookie を提供することは不可能です。」

こりゃ対応されないわ

## どういうこと？

Node.jsではv19あたりでFetch APIが実装されて
特にrequireやimportしなくても使えるようになった

そのNode.jsの有り難い定義がこちら

https://nodejs.org/docs/latest/api/globals.html#fetch

> A browser-compatible implementation of the [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) function.

> 和訳: ブラウザの挙動に従うよ、MDNのリンク張っておくから見てね

薄っぺらすぎて泣いちゃう

そもそもFetch APIはブラウザに搭載してある機能であり
そのブラウザがセキュリティのために、向こう側のWebサーバが準備したHeader情報を削り落として見えなくしたというなら
Node.jsで動くFetch APIも情報取れたらおかしいよねって話

従ってNode.jsの開発者達は「どうせ削り落とされるんだから最初から実装しねえよ」で終わってるわけ

たぶんね

別ライブラリ探してみようかね

# 最後に

これは情報をつなぎ合わせた推測なので、
本当の所を知ってる人が居たら、こっそりコメント欄で教えてください。