ishikawa_pro's memorandum

若手webエンジニアの備忘録です.

Swift Node.js Docker AWS etc...色々やります。

CORS(オリジン間リソース共有)・Preflight Request まとめ

こんにちは。
だいぶ秋らしくなってきましたね。
今日はCORSやPreflight Requestについてまとめてみます。

iOSアプリ用にAPIサーバーは作ったことがあったけど、フロントエンド向けにAPIサーバーを作ったことがなくて、ブラウザのCORSは全然知らなかったので、ちゃんとまとめておこうという感じです。

CORSとは?

オリジン(ドメイン)をまたいでリソースを共有すること。
要するにXMLHttpRequestやFetch APIで別ドメインAPIサーバーにアクセスすることです。
XMLHttpRequestやFetch APIは基本的に同一オリジンにしかリクエストできないですが、CORSの設定をすることで許可されたクロスオリジンにリクエストが可能になります。

CORSを正しく設定していないとCSRFで攻撃されてしまう可能性があるから、なんとなくクライアントを守るような仕組みだと勘違いしていましたが、守る視点はAPIサーバー側です。
設定もフロントエンドでは特にすることはなく、基本的にサーバー側で設定して、フロントはサーバー側の設定にしたがってブラウザが振る舞いを決めるだけです。
APIサーバーを守るためのルールだという視点があれば、MDNのドキュメントを見てもピンとくると思います。

Preflight Requestとは

Preflight Requestは、実際の通信の前に権限確認のため送信するリクエストです。Preflightが必要かブラウザが判断してリクエストを送ります (後述)。 先ほども書きましたがブラウザが自動でやるので、フロントエンド側が手動で送る必要はありません。
PreflightはOPTIONSメソッドを使って、許可されているMethodとHeaderを問い合わせます。

simple or actual

Preflight Requestは、必要な場合とそうでない場合があります。
下記条件はsimple cross-origin requestといい、Preflight Requestは必要ありません。

  • GET, HEAD, POST メソッド
  • HeaderがCORS-safelisted request header のみ
  • Content-Typeを含む場合に値が、 application/x-www-form-urlencode, multipart/form-data, text-plain のどれか

CORS-safelisted request header に該当するヘッダー名は以下の4つです。

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type

上で述べた条件に当てはまらない場合は、Preflight Requestが必要になります。

MDNのCORSのページを見ると、DPR, Downlink, Save-Data , Viewport-Width, Width というclient hintsも含んでいるように書いていたけど、ググっても他でclient hintsに関する記述をしてるところがなかったので、どなたか知ってる人いれば教えてください。 developer.mozilla.org

リクエストヘッダー

Preflightが発生する場合、クライアントは以下のリクエストヘッダーをつけてOPTIONSメソッドでPreflight Requestを飛ばします。

  • Access-Control-Request-Method
    • 通信を許可してもらいたいメソッドを指定
  • Access-Control-Request-Headers
    • 許可してもらいたいヘッダーをカンマ区切りで列挙
  • Origin

Access-Control-Request-Headers に、上で述べた CORS-safelisted request header は記述不要です。それ以外のカスタムヘッダーなどを追加する場合は列挙が必要です。

レスポンスヘッダー

サーバー側はクライアントからのPreflight Request を受けて、許可するメソッドなどを以下のレスポンスヘッダーで示します。

  • Access-Control-Allow-Origin
    • リソースへのアクセスを許可するオリジン
    • どのオリジンからもアクセスを許可する場合は * を指定
  • Access-Control-Allow-Methods
    • 対象のURLに許容されるメソッド
    • Preflightが不要なメソッドは省略されることがある
  • Access-Control-Allow-Headers
    • 対象のURLに許容されるヘッダー名のリスト
    • CORS-safelisted request header は省略されることがある
  • Access-control-Expose-Headers
    • サーバーから返すレスポンスヘッダーのうち、スクリプトから参照できるヘッダー名の一覧を返す
    • デフォルトでは、 Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma のみ(CORS-safelisted response header)
  • Access-Control-Allow-Credentials
    • クッキーなどの資格情報をサーバーが受け取ることを許可する時に付与される
    • 値はtrueのみ設定可
  • Access-Control-Max-Age
    • Preflight Requestのキャッシュ可能秒数

ブラウザは、上記の内容をみて通信が可能かどうかを判断しています。
サーバー側は、許容しない内容がある場合はレスポンスヘッダーに含めなかったり、401 Forbiddenのステータスを返したりして対応します。

まとめ

上記でまとめたようなPreflight Requestを送ったり送らなかったりしながら、クロスオリジン間でブラウザは通信をしています。
ブラウザのデベロッパーツールで通信を覗くとOPTIONSメソッドでPreflight Requestが飛んでいるのを見ることができます。

感想

僕は入社してすぐの研修で作ったアプリケーションでは、ブラウザでCORSのエラーが出て適当にAccess-Control-Allow-Originに * をつけて対応したような気がします笑
実務でも誰かがいい感じに設定したexpress.js用のmiddlewareをなんとなく使ったり、OPTIONSメソッドがないとなんかフロントがうまくAPI叩けないというざっくりとした認識でしたが、しっかり知識として落とし込めてよかったです。
今回の内容はMDNのドキュメントやReal World HTTPを参照しながらまとめました。

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

頑張って最低週1でブログ書くように頑張ってるけど、続くか心配・・・
来週も頑張ります。