🧊

動的に`Access-Control-Allow-Origin`をレスポンスする場合は、`Vary: Origin`も忘れずに

タイトル通りではあるけど、結構「?」がいっぱいになった出来事だったので。

ことのあらまし

  • とある静的リソースをレスポンスするAPIがあった
  • リクエストヘッダーのOriginを見て、アクセスを制限していた
  • リクエストを許可する場合は、Access-Control-Allow-OriginOrigin値を指定

という構成。

簡単なコードで書くとこのような。

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:8080localhost: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付けないとあかんやつやんって思うけど・・。