はじめに
FlaskとReactで簡単なアプリを作成している時に、Webサーバー(React)からPOSTでリクエストしてるのにAPIサーバー(Flask)にはOPTIONSしか届かない問題が起きました。
少しでもAPIを触ったことのある方は「APIサーバー側でCORSの設定してないからでしょ」とすぐにわかる問題かと思いますが、初めてだと私のようにつまづく方も多いのではないかと思います。
この記事では、POSTをリクエストするとOPTIONSがリクエストされる理由とその仕組みを、問題を解決するうえで学んだことをふまえて簡単に説明したいと思います。
要約
- OPTIONSは、POSTなどのリクエストの前にブラウザーなどによって自動で行われることがある
- OPTIONSは、Preflighted requests(試験飛行)で使用されるメソッドである
- POSTを受け取る側(APIサーバー)で、ヘッダーに対してCORSの設定をすれば解決する
- Access-Control-Allow-Originでクライアントのオリジン(URL)を指定
- Access-Control-Allow-HeadersでHTTPヘッダーを指定
- Access-Control-Allow-Methodsでメソッド(GET,PUT,POST,DELETEなど)を指定
- Access-Control-Max-Ageでキャッシュを許可する期間を指定
- とりあえず
Access-Control-Allow-Origin: *
という解決方法は良くない
ターゲット
- API開発でつまづいている(特にPUT,POST,DELETE)
- リクエストするときのメソッドとヘッダーの意味について簡単に知りたい
- REST API(RESTful API)やHTTPメソッドについて勉強中の初心者
OPTIONSを理解する前に必要な知識(CORSについて)
OPTIONSはCORSで使用されるリクエストの一つです。
まずは、CORSを理解しましょう。
※既に理解している方は読み飛ばして頂いても構いません
CORS(Cross-Origin Resource Sharing)
オリジン間リソース共有Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。
MDN Web Docs - オリジン間リソース共有 (CORS)より引用
CORSは日本語で「オリジン間リソース共有」と呼ばれています。
仕組みを理解するために、「ブラウザー」「あるオリジンのウェブアプリケーション」「異なるオリジンにある選択されたリソース」の関係性を確認します。
この図で例えると以下です。
- Webブラウザーが「ブラウザー」
- あるオリジンは
https://example-main.com
で、Mainサーバーで動いているのが「あるオリジンのウェブアプリケーション」 - 異なるオリジンは、
https://example-x.com
で、イメージ1が「異なるオリジンにある選択されたリソース」
まず、最初のリクエストでメインとなるオリジンが決定します。今回は最初にMainサーバーへリクエストしているのでhttps://example-main.com
がメインのオリジンです。
その後のコンテンツ1のリクエストは、同一オリジンなのでSimple requests(単純リクエスト)で取得します。
イメージ1は、オリジンがhttps://example-x.com
です。メインとは異なるオリジンなので、リソースへのアクセス許可が必要です。リソースを所有する側(https://example-x.com
)がアクセス許可を出すことでイメージ1が使用できます。これがCORSです。
Same Origin PolicyとCORS
メインのオリジンで異なるオリジンからリソースを読み込むことは、リソースを提供する側からアクセス許可をもらう許可制となっています。
図の例だと、https://example-x.com
がhttps://example-main.com
に対してイメージ1の使用を許可しないと、ブラウザーの仕組みでブロックされます。
なぜなら、https://example-main.com
が悪意のあるオリジンでhttps://example-x.com
に対してのリクエストが許可なしに行えてしまったら、簡単に攻撃が成立してしまうからです。
このようにセキュリティ上の問題から、基本的には同じオリジンからしかリソースを取得しないという方針を、**Same Origin Policy(同一オリジンポリシー)**といいます。
つまり、Same Origin Policyが基本方針で、異なるオリジンのリソースを使いたいときは、CORSという仕組みを利用します。
ブラウザーがSame Origin Policyを守りつつ、CORSで許可したオリジンしかリソースを使用できないようにすることで、セキュリティを高めています。
OPTIONSの役割
先に結論からいうと、異なるオリジンからリソースへのアクセスが許可されているかを確認するためのメソッドです。
こちらの条件を満たす場合は、OPTIONSは送信されません。これは俗にSimple requests(単純リクエスト)と呼ばれています。それに対してPreflighted requests(プリフライトリクエスト・試験飛行)があり、この通信で用いられるメソッドがOPTIONSです。
OPTIONSはある条件でブラウザーがリクエストを自動的に送信します。プリフライトリクエストでは、確認内容をヘッダーフィールドに指定します。
リクエストで指定する内容とそれに対するサーバーのレスポンスはそれぞれ以下です。
リクエスト
- Access-Control-Request-Method
- 許可を要求するメソッド(GET,PUT,POST,DELETEなど)
- Access-Control-Request-Headers
- 許可を要するヘッダー(Content-Type,Authorizationなど)
レスポンス
- Access-Control-Allow-Origin
- リソースへのアクセスを許可するオリジン
- Access-Control-Allow-Methods
- 許可するメソッド
- Access-Control-Allow-Headers
- 許可するヘッダー
- Access-Control-Max-Age1
- プリフライトリクエストの結果のキャッシュを許可する期間(単位は秒)
Simple requestsの例
https://mdn.mozillademos.org/files/17214/simple-req-updated.pngより引用
Simple requestsでは、リクエストでリクエスト元のオリジンをヘッダーのOrigin
に指定します。それに対して、サーバーはレスポンスでAccess-Control-Allow-Origin
とともにリソースを返します。図で指定されている*
はワイルドカードです。
Preflighted requestsの例
https://mdn.mozillademos.org/files/17268/preflight_correct.pngより引用
上の図はPOSTでxmlを送信する例です。Preflighted requestsは、OPTIONSでこれからリクエストするメソッドと使用するヘッダーが許可されているかを確認します。
この図の例では、メソッドはPOSTでヘッダーはX-PINGOTHER
とContent-type
の通信が許可されているかを確認しています。
それに対して、サーバーはレスポンスでAccess-Control-*
を用いて何が許可されているかを返します。
Preflighted requestsで許可されていることが確認できたら、メインのリクエストを送信します。図では、許可された二種類のヘッダーを用いて、POSTで通信しています。
注意すべきこと
私がOPTIONSが理解できておらず、調べていてよく目にした解決策はAccess-Control-Allow-Origin: *
でした。つまり、どのオリジンに対しても許可するという方法です。
しかし、何も考えずに全てのオリジンに対して許可するのは良い方法とは思いません。
(解決策を提示している方々やそれ自体を非難するつもりはないです。それを見て、何も考えずに同じ方法を使用するのが良くないという意味です。)
Same Origin Policyが各ブラウザーのデフォルトに選ばれている理由(セキュリティ面)や、CORSの意味とその仕組みを理解した上で、適切な設定を行いましょう。
おわりに
OPTIONSを説明するだけのつもりでしたが、少し重たい記事となってしまいました。
それでも最後まで読んで頂いた方々は、本当にありがとうございます。
OPTIONSだけでなく、CORS、Same Origin Policy、Preflighted requestsを理解する上で少しでも助けになっていれば幸いです。
参考
- MDN Web Docs - オリジン間リソース共有 (CORS)
- MDN Web Docs - CORS-safelisted request header
- MDN Web Docs - Access-Control-Max-Age
-
各ブラウザーの上限値を超えて設定することはできません。(Chrome: 10分, Safari: 5分, Firefox: 24時間)
条件を見ただけでは分かりづらいので、Simple requestsとPreflighted requestsを図で見てみます。 ↩