コンテンツにスキップ

クロスオリジンリクエスト

[security.cors]
enabled = true
allowed_origins = ["https://app.example.com"]

この機能が存在する理由になっているデプロイ — API はここ、ブラウザアプリケーションは 別のどこか、あいだを Bearer トークンが通る — なら、これで全部です。

既定で無効なのは、たいていのデプロイが自分を読むページを自分で配信しているからです。 アドレスバーのオリジンと fetch に答えるオリジンが同じなら、ブラウザは誰の許可も求めず、 このセクションは何の効果もない設定になります。自分が配信していないページに、自分が 返したレスポンスを読ませる必要が出たときに有効にしてください。

ブラウザが握りつぶしているもの

Section titled “ブラウザが握りつぶしているもの”

これがないとき、クロスオリジンの fetch はサーバ側で派手に失敗したりしません。 リクエストは届き、ハンドラは走り、レスポンスは出ていく — そしてブラウザが、 それを要求したスクリプトに何も渡さない。ボディも渡さないし、ステータスも渡しません

回避策で済ませずに設定する価値があるのは、この最後の一点です。マシンクライアントに 正確に答えるためにこのフレームワークがやっていること — 型付きの problem レスポンス、トークンの期限切れを意味する 401Retry-After を携えた 429 — が、クロスオリジンの呼び出し元には区別のつかない 1種類のネットワークエラーとして届きます。期限切れのトークンとレートリミットと サーバダウンを区別できないので、正しくリトライできない。そしてデプロイ側には何も見えません。 自分のアクセスログには、成功したリクエストが記録されているのですから。

この枠は、リクエストを拒否しうるスタックのどの部分よりも上に置かれています。 成功したレスポンスだけでなく、拒否したレスポンスにも印が乗るのはそのためです。 プロセスレートリミットの 429、CSRF チェックの 403、panic の 500 — どれも 読める形で呼び出し元に届きます。

[security.cors]
enabled = true
allowed_origins = ["https://app.example.com"]
allowed_methods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"]

allowed_methods の既定は GETHEADPOST の3つ — ブラウザが事前に問い合わせずに 送るものだけ — なので、書き込み系まで広げるのは明示的な宣言になります。 allowed_headers の既定は Content-TypeAuthorization。トークンを携えた クライアントが送るものそのもので、それ以上ではありません。

ここに Cookie は関与しないので、CSRF チェックの 話は一切かかりません。名前を挙げたオリジンに与えているのは、そもそも全リクエストが 自分で身元を証明する API の読み取り権です。まずこの設定を選んでください。

allow_credentials = true は、ブラウザがこのデプロイの Cookie を付けて送った レスポンスを、リストにあるオリジンに読ませます。上の設定の「広い版」ではありません。 別種の付与であって、それ自体の検討を要します。

[security.cors]
enabled = true
allowed_origins = ["https://console.example.com"]
allow_credentials = true
include = ["/api/**"]

include の行は必須です。資格情報を有効にしたまま範囲を絞らないと、名指ししたオリジンに サインイン中の訪問者が見られるものすべて — 認証済みページ全部、部分更新の fetch 全部、 ライブ領域のストリーム — の読み取り権を渡すことになります。起動時に include = ["/**"] を拒否するのは、あとで発見される類の事故だからです。

一方、書き込みは付与されません。CSRF トークンは同一オリジンのスクリプトしか 読めない Cookie を通るので、別オリジンのページはチェックが要求するヘッダを付けられず、 このセクションが何と言おうと unsafe なリクエストは 403 で拒否されます。資格情報を 有効にしてクロスオリジンの POST が通ると期待すると、着地点はその 403 です。 しかもブラウザはそれを CORS の失敗として報告するので、たいていの人はこのセクションを 編集しに戻ってきます。答えはもう片方にあるのに。

正直にまとめるなら、資格情報が与えるのはクロスオリジンの読み手であって、 書き手ではありません。別オリジンからの書き込みが要るなら、機構は Cookie ではなく Bearer トークンです。

拒否したリクエストはここからどう見えるか

Section titled “拒否したリクエストはここからどう見えるか”

クロスオリジンの失敗はサーバ側から見えないことで有名なので、これは見えるようにしてあります。 リストにないオリジンは普通に処理され — 何も拒否していません、断るのはブラウザです — 何が一致しなかったかを名指しするレコードが1本だけ書かれます。

level=INFO msg="cors policy declined a request" reason=origin origin=https://app.example.com path=/api/things

reasonoriginmethodheader のいずれか。レスポンスはどれなのか言いません。 どうせブラウザが答える質問ですし、精密な拒否は呼び出し元がポリシーを地図に起こすのを 助けるだけだからです。しかしログは言います。5分で終わる修正と、午後を溶かす調査の差が そこにあります。

レコードは毎秒10本で上限を設けています。オリジンを決めるのは呼んでいる側だからです。 超えた分は捨てられ、次に通ったレコードが dropped の件数を携えます。

OpenAPI ドキュメントには何も要りません

Section titled “OpenAPI ドキュメントには何も要りません”
[server]
openapi = "/openapi.json"

生成された OpenAPI ドキュメントは、このセクションの有無にかかわらず、自分で Access-Control-Allow-Origin: * を返します。公開すると既に決めた契約の記述であり、 訪問者ごとに変わるものを何も持たず、そして事前に列挙できないツール — 別の場所に ホストしたドキュメント UI、クライアントジェネレータ、CI のリンタ — に読まれるからです。 ワイルドカードは資格情報を禁じるので、認証の後ろに置いたドキュメントでも、 クロスオリジンの読み手には未認証時の応答が返るだけで、何も漏れません。

オリジンのリストを設定しているあいだ、範囲内のレスポンスはすべて Vary: Origin を 携えます。このポリシーが印を付けなかったレスポンスも含めて、です。冗長に見えますが、 そうではありません。判断は Origin ヘッダを読んでいます。何も書かないという答えを 出したときも読んでいる。何も鍵にしない共有キャッシュは、あるオリジンへの許可を、 拒否されたはずの呼び出し元に手渡します。

例外は allowed_origins = ["*"] だけです。どの呼び出し元にも同じ答えを返すので Vary は付かず、そのままキャッシュできます。この形が使えるのは資格情報が無効なときだけ。 仕様がそう要求していて、起動時にも強制します。

3つあります。このページに辿り着くデプロイのほとんどはこのどれかです。

ページと API が同じオリジンにある。 ここに書かれたことは何もかかりません。 セクションを足してもレスポンスは変わりません。

アプリケーションの手前で既にやっている。 自前の CORS 設定を持つ CDN や ingress は これをやるのに適した場所ですし、同じヘッダを2層が書くのは冗長ではなく衝突です。 どちらか一方にしてください。それでもフレームワークにこれがあるのは、pw dev の前に そんな層が無いからです。クロスオリジンのクライアントを書いている開発者は、設定する 場所が何も無いまま、手元で最初に失敗にぶつかります。

呼び出し元がブラウザではない。 サーバ、CLI、ジョブランナー、モバイルアプリの ネイティブ HTTP クライアント — どれもこれを一切強制しません。CORS はブラウザの制約です。 それを無視するクライアントのために設定しても、何も買えません。

この問題に見えて違うものが、もう1つあります。1つのアプリケーションの手前に2つ目の ホスト名を置く話です。欲しいのが既存の API を自分のページから叩けるようにすることなら、 そのアプリケーションの内側にマウントすればこの話題ごと回避できます — サービスプロキシのガイドを参照。

WebSocket のアップグレードは、そもそも CORS の対象外です。preflight も Access-Control-Allow-Origin も無しにオリジンをまたいで Cookie を運びます。防御は WebSocket ガイドにあるオリジンチェックです。 このセクションでオリジンを許可してもその経路では何も与えませんし、拒否しても 何も奪いません。

キーの一覧は設定リファレンスにあります。