logo
Published on

CORS徹底解説

Authors

はじめに

対象読者は、CORSを知らないバックエンドエンジニアです。
この記事のゴールは、CORSエラーを自力で解決できるようになることです。

Table of Contents

同一オリジンポリシーとは

まず前提として「同一オリジンポリシー(以降SOPと記載)」を理解しておく必要があります。
SOPはあるオリジンから読み込まれた文書やスクリプトは他のオリジンにあるリソースにアクセスできない、というブラウザのセキュリティルールです。
たとえば、https://my-site.invalidで読み込まれたスクリプトからhttps://my-site.invalid/api/articlesへアクセスできますが、https://api.my-site.invalid/articlesへはアクセスできません。

オリジンは、スキーム(httpやhttps)、ホスト(ドメイン名)、ポート番号の組み合わせで定義されます。
https://my-site.invalid/api/articlesのオリジンはhttps://my-site.invalid1です。
オリジンが完全一致した場合に同一オリジンとみなされます。
先ほどの例だと、https://my-site.invalidhttps://api.my-site.invalidではオリジンが異なるため、アクセスできないということです。

CORSとは

CORSはCross-Origin Resource Sharingの略で、異なるオリジン間でリソースを共有するための仕組みです。
要するにSOPの制限を回避するわけです。
そのために、特定のHTTPヘッダーを用いてサーバーがどのオリジンからのリクエストを許可するかを明示します。

SOP自体はセキュリティ上重要ではあるのですが、現実問題として異なるオリジン間で通信したいケースは一般的です。
たとえば、フロントエンドhttps://my-site.invalidがAPIサーバーhttps://api.my-site.invalidにアクセスするといったケースはよくあります。
CORSはそのようなケースで、セキュリティを保ちながらリソースを共有するための仕組みとして利用されます。

CORSの仕組み

CORSの契機は異なるオリジンからのHTTPリクエストです。
このリクエストはシンプルリクエストとプリフライトリクエストに分類されます。
前者は文字通りシンプルで、後者はやや複雑です。

シンプルリクエスト

シンプルリクエストは、以下の条件を満たすリクエストです。

このシンプルリクエストに対して、サーバー側で何も準備をしていないとCORSエラーが発生します。
このリクエストを許可するには、サーバー側でAccess-Control-Allow-Originヘッダーに許可したいオリジンを指定してレスポンスを返す必要があります。 たとえば、https://my-site.invalidからのリクエストを許可する場合、以下のようにヘッダーを設定します。

api.my-site.invalidサーバーがmy-site.invalidからのリクエストを許可する例
Access-Control-Allow-Origin: https://my-site.invalid

これでCORSエラーが発生せず、https://my-site.invalidは無事にhttps://api.my-site.invalidと通信できるようになります。

このヘッダーの値に*を指定すると、すべてのオリジンからのリクエストを許可します。
しかし、一般公開されたAPIなど*にしないといけない理由がない限り、セキュリティ上の理由から特定のオリジンを明示することが推奨されます。

プリフライトリクエスト

シンプルリクエストの条件を満たせない場合、ブラウザはプリフライトリクエストを送信します。
シンプルリクエストと異なり、リクエストの許可を得るためだけのやり取りをします。

  1. ブラウザはHTTPメソッドOPTIONSを使用してプリフライトリクエストを送信する。このとき、以下のようにどんなリクエストを許可してほしいかを示すヘッダーを含める。
    • Access-Control-Request-Method: 使いたいHTTPメソッド(例: DELETE
    • Access-Control-Request-Headers: 使いたいHTTPリクエストヘッダー(例: Content-Type
  2. サーバーはアクセスを許可するオリジンやHTTPメソッド、HTTPリクエストヘッダーを指定してレスポンスを返す。
    • Access-Control-Allow-Origin: 許可するオリジン(例: https://my-site.invalid
    • Access-Control-Allow-Methods: 許可するHTTPメソッド(例: GET, POST, DELETE
    • Access-Control-Allow-Headers: 許可するHTTPリクエストヘッダー(例: Content-Type
  3. ブラウザはサーバーからのレスポンスを見て、リクエストを続行するかどうかを判断する。たとえばHTTPメソッドDELETEを使いたいのに許可されていない場合はCORSエラーを発生させる。

シンプルリクエストにせよプリフライトリクエストにせよ、サーバー側で適切な設定を行えば、不要なCORSエラーを発生させないで済みます。
ここまでの説明で「設定が面倒くさそうだなー」と思った方、安心してください。
リクエストはブラウザが、レスポンスはWebアプリケーションフレームワークがある程度よしなにやってくれます。
今回紹介した仕組み部分をきちんと把握しておけば、CORSエラーが発生したとしてもその修正自体はそこまで難しくありません。

トラブルシューティング

ここまでの知識があれば、CORSエラーが発生したときにどのように対処すればよいかがわかるはずです。
実際にそうか、よくあるエラーメッセージの要約をいくつか紹介します。

  • Access-Control-Allow-Originが含まれていない: 許可したいオリジンを同ヘッダーに指定する。
  • プリフライトレスポンスによるとPUTが許可されていない: Access-Control-Allow-MethodsにPUTを追加する。
  • Authorizationヘッダーをリクエストに含める許可がサーバーから得られていない: Access-Control-Allow-Headersに同ヘッダーを追加する。

CORSのことを知らないと意味不明だと思いますが、CORSの仕組みを理解していれば、エラーメッセージから何をすればよいかがわかるはずです。

まとめ

  • 同一オリジンポリシーとは、あるオリジンから読み込まれた文書やスクリプトは他のオリジンにあるリソースにアクセスできない、というブラウザのセキュリティルールのこと
  • CORSとは、異なるオリジン間でリソースを共有するための仕組み
  • シンプルリクエストとプリフライトリクエストお理解が鍵
  • CORSエラーが発生したときは、エラーメッセージをもとにサーバー側の設定を見直す

Footnotes

  1. ポート番号が見当たりませんが省略されているだけです。ここではHTTPSなので443です。ちなみにデフォルトポート番号を色々覚えているとマウントが取れて便利です。

  2. 値はapplication/x-www-form-urlencodedmultipart/form-datatext/plainのいずれかです。フォームが優遇されていますね。