タイトル通りではあるけど、結構「?」がいっぱいになった出来事だったので。
ことのあらまし
- とある静的リソースをレスポンスするAPIがあった
- リクエストヘッダーの
Origin
を見て、アクセスを制限していた - リクエストを許可する場合は、
Access-Control-Allow-Origin
にOrigin
値を指定
という構成。
簡単なコードで書くとこのような。
const originHeader = req.headers.get("origin");
if (!canAccess(originHeader))
return new Response("Not Found", { status: 404 });
return new Response(getAssets(), {
status: 200,
headers: {
"Access-Control-Allow-Origin": originHeader,
},
})
これだけ見ると、*
で良くも見えるが、まあ。
問題になるとき
- Chromeでアクセス
- 複数のサイトから、同じリソースへリクエスト
したときに、後からアクセスした方がCORSのエラーになる。
特定のリソースを、localhost:8080
とlocalhost:8081
からリクエストしたときに、こういうエラーになる。
Access to CSS stylesheet at '(XXX)' from origin 'http://localhost:8080' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header has a value 'http://localhost:8081' that is not equal to the supplied origin.
(XXX)
のところが対象のリソース。
端的に受け取ると、本来localhost:8080
向けのレスポンスが、なぜかlocalhost:8081
で返ってる・・・?ってなる。
そしてこれが、Chromeでだけ発生し、FirefoxやSafariでは、少なくとも手元では再現しなかった。(なんならChromeでも再現したりしなかったりだった)
DevToolsでキャッシュを無効にすると再現しなかったりと、まあキャッシュ関連のそういう話なんやろう。
CORS-preflight cache
なんやかんや調べてたどり着いたのがこれ。
CORS Preflight Cache Does not Consider Origin [41025985] - Chromium https://issues.chromium.org/issues/41025985
で、これはバグではなく仕様ということらしく、
The Fetch Standard recently added more explanation about preflight result caching. Basically you need to either:
- always include “Access-Control-Allow-Origin: *” for a resource that can be accessed using CORS
- add the Vary header not to get responses cached and reused for requests from other origins.
というわけで、動的にやるならVary
ヘッダーを付けなさいってことらしい。
Fetch Standard https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches
そういうわけで
const originHeader = req.headers.get("origin");
if (!canAccess(originHeader))
return new Response("Not Found", { status: 404 });
return new Response(getAssets(), {
status: 200,
headers: {
"Access-Control-Allow-Origin": originHeader,
"Vary": "Origin", // 👈🏻
},
})
とすれば安心。もしくは、"Access-Control-Allow-Origin": "*"
だけにするか。
まあ今改めて見ると、ちゃんとVary
付けないとあかんやつやんって思うけど・・。