CORS と Cookie についてたくさん調べたので書くぞ。
やろうとしたこと
カイリューさんが開発された Web アプリ、AtCoderList の Google 拡張機能を作ろうとした。
AtCoderList は、AtCoder で解きたい問題とか解いている途中の問題とかをリストにして管理できるようにするというもの。
自分が作りたかった拡張機能は、AtCoder のページからボタンひとつで AtCoderList に問題を登録するというもの。
こんな感じ。
想定していた実装
- ボタンを押す
- DOM から問題情報を取得してリクエストを作成
- リクエストに AtCoderList の Session ID (Cookie) を込めて リクエストを送る
という感じ。ログインしているユーザーに対して問題を追加したいので、Session ID を込める必要がある。
こんなの簡単だよー!と思って実装に取り掛かったのだが、このときの自分はこれから CORS という罠にハマるとは思いもしなかったのである...。
クライアントの実装
async function postData(url = '', data = {}) {
fetch(url, {
method: 'POST',
mode: 'cors',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
}).then(response => {
console.log(response)
return response.url
})
}
意気揚々と MDN Web Docs のサンプルコードを丸パクリし、いざ実行してみると、なんか動かん。
Access to fetch at 'http://atcoder-list.herokuapp.com/create' from origin 'https://atcoder.jp' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
あー、CORS...なんか聞いたことある...というレベルの知識だったので、ここで初めて CORS をちゃんと調べる。
オリジン間リクエストとは、例えば https://domain-a.com で提供されているウェブアプリケーションのフロントエンド JavaScript コードが XMLHttpRequest を使用して https://domain-b.com/data.json へリクエストを行うような場合です。
セキュリティ上の理由から、ブラウザーは、スクリプトによって開始されるオリジン間 HTTP リクエストを制限しています。例えば、 XMLHttpRequestや Fetch API は同一オリジンポリシー (same-origin policy) に従います。つまり、これらの API を使用するウェブアプリケーションは、そのアプリケーションが読み込まれたのと同じオリジンに対してのみリソースのリクエストを行うことができ、それ以外のオリジンからの場合は正しい CORS ヘッダーを含んでいることが必要です。
つまり、AtCoder のページから AtCoderList にリクエストを飛ばすためには、AtCoderList が"正しい CORS ヘッダー"とやらを返さなければならないらしい。
AtCoderList に PR 送るか!と、AtCoderList (サーバーサイド) の修正に取り掛かる。
サーバーサイドの実装
app.use(cors(function (req, callback) {
var corsOptions;
corsOptions = {
origin: "https://atcoder.jp",
credentials: true,
};
callback(null, corsOptions)
}))
上のコードは、以下のような HTTP ヘッダを付加するミドルウェアの追加を行なっている。
- https://atcoder.jp からの CORS を許可する
- クライアント側が持っている資格情報 (Cookie など) をリクエストに込めさせる
2 つ目の条件により、AtCoderList の Session ID を込めた状態でリクエストが送って来られるようになるはず。これで完璧!と思っていたのに...。
なんか Cookie が送られて来ないんだけど
おかしい...ちゃんとサーバー側、クライアント側ともに Cookie が送られるような設定をしているのに...。さらに調べてみると...
2 月の Chrome 80 以降、SameSite 値が宣言されていない Cookie は SameSite=Lax として扱われます。外部アクセスは、SameSite=None; Secure 設定のある Cookie のみ可能になります。
なんか難しいこと書いてある。
SameSite とは、Cookie に対して他のドメインに対して送りますかということを決める属性。つまり、拡張機能から AtCoderList の Session ID を送るには、SameSite=None としたい。
ところが、SameSite=None とするためには、Secure 属性も同時に付加しなければならない。
Secure 属性が付くと、その Cookie は HTTPS 通信上でのみセットされることになる。
でも、AtCoderList は HTTPS に対応していない...。つまり Session ID を送ることはできない。詰みである。
まとめ
色々試行錯誤してわかったこと
- AtCoder から AtCoderList に Session ID を送りたい
- そのためには Session ID に SameSite=None; Secure の属性をつけなればならない
- Secure を付けると HTTPS 上でしかセットされない
- AtCoderList は HTTPS 対応してない
- よってクライアントからサーバーに Session ID を送ることができない
悲しい。
参考