はじめに
開発環境にデプロイする際にセキュリティの観点から「APIを叩いてもCORSで弾かれた」ため、それに伴うアウトプットをします。
※個人ブログから技術的アウトプットはQiitaへ引っ越ししたので、こちらは過去に書いたブログとなります。
エラーの事象
開発環境にデプロイする際にセキュリティの観点から
「 APIを叩いてもCORSで弾かれた 」です。
背景
まず冒頭の
開発環境にデプロイする際に
ということですが、今までローカル環境では、nuxt.config.js
の設定でproxyサーバー
を立てることができていたので、サーバー側でCORS対応をする必要ありませんでした。
しかし、Firebaseのhostingにデプロイした際にproxyサーバーを立てることができずに、結果、冒頭に書いたAPIを叩いてもCORSで弾かれました。
調べてみると、セキュリティの観点から、オリジン(HTMLが置かれたサーバー)以外のサーバーからデータを取得すること(=CORS:Cross-Origin-Resource-Sharing)を、現在のブラウザでは許可していないようです。
こちらを回避するためには、サーバー側にて、CORSを許可する必要とのこと。
CORSとは
そもそもCORS、CORSと書いてるが、CORSとはなにか?
僕は先日始めてこの英単語の組み合わせ達に鉢合わせました。
- 読み方: コルス
-
Cross-Origin Resource Sharing
の略 - 訳すと「 オリジン間リソース共有 」
ではこのオリジンとはなにか??
- オリジンは ドメイン と
似た概念似てるようで全く異なる概念 - ドメイン(例):google.com
- オリジン(例):https://www.google.com:443
- つまり、ドメインとの 見た目上の違い はプロトコルとホストとポート番号を含んでいるという点
オリジン == プロトコル + ホスト + ドメイン + ポートナンバー
※ URLを構成する要素を整理について
※ クエリストロングではなく、 クエリストリング(文字列)
です....笑 何を強くしてるねん....
URLを構成する要素を整理についてはこのツイートがわかりやすい
※ クエリストロングではなく、 クエリストリング(文字列)だと思われる
URLを構成する要素を整理するとこうなる!
— 東 莉緒 / Rio Azuma (@rio310mink) September 23, 2020
デザインの勉強するまで深く考えたことなかった。笑 pic.twitter.com/kyGRMS1Xxk
オリジンを踏まえた上でCORSとは?
-
CORS は日本語訳すると オリジン間リソース共有 (上述)。
-
つまり CORS とは、あるオリジンで動いている Web アプリケーションに対して、別のオリジンのサーバーへのアクセスをオリジン間 HTTP リクエストによって許可できる仕組みのことをいう
-
許可できるようになるまでの仕組みとしては、サーバーからのレスポンスにリソースの共有を許可するための特別なヘッダーを追加して動作できるようになる
なぜCORSが必要なのか?
これは上述のように、昨今のブラウザでは、フロントエンドJavaScriptから違うドメインへのアクセスに対して、CORSがサーバ側で許可されている場合を除き、セキュリティ上の問題からアクセスをしない仕様となっているため。
どんな時にクロスオリジンにアクセスするか?
基本的には、
- XML HttpRequest(Ajax)
- FetchAPIでの非同期通信する時
などって思ってOK
CORSのリクエストの種類
XMLHttpRequestを用いてオリジン間リソース共有がどのように動作するかを説明
リクエストによっては CORSを引き起こさないものがあるみたい。
そこでリクエストを2つ(単純リクエストとプリフライト リクエスト)に分けます。
▼ 単純リクエスト
上述通り、CORSを引き起こさないもの。
例えば以下のメソッド
- GET
- POST
- HEAD
▼プリフライト リクエスト
単純リクエストとは異なり、リクエストの始めに OPTIONSメソッド
で対象の異なるオリジンにリクエストを送り、実際のリクエストを送っても問題ないか確認するリクエストをプリフライト リクエストという
該当するリクエストは以下
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
HTTPのOPTIONSメソッドとは
- HTTPで使われるメソッドといえば、上記のようなGETやPOSTが一般。
- 一方、
OPTIONSメソッド
は、これらのメソッドのうち、サーバーがどのメソッドをサポートしているかを調査するためのメソッド。 - どこで使われるのかと言うと、ブラウザがCORSを検出した場合、実際のメインメソッド(GETやPOST)を投げる前に、OPTIONSメソッドによる検査を実行するような仕様になっています。そしてプリフライトリクエストを投げて、そこからOKであれば、204でレスポンスが返ってきて、そこからメインリクエスト(GETメソッド)が返ってくる。つまりレスポンスが2個返ってくる!(下図)
つまり、APIサーバの実装には
- CORSを有効にする(HTTPレスポンスヘッダにAccess-Control-Allow-Origin等の実装)に加え、
- OPTIONSメソッドの実装
が必要ということ
express で CORS を許可する
今回は、CORS とはなにか?が中心だったので、最後にかんたんにコードで説明します。
CORSを許可するための実装には、
- XHR を使う場合
- Fetchを使う場合
- axios を使う場合
- Expressを使う場合
などがあり、開発推進課ではNodeのExpressを使っているので、それを使ってCORS を許可する。
▼cors モジュールをimport
import cors from 'cors';
app.use(cors());
ちなみに
app.use(cors());
だと フルで CORS 周りの設定を許可していることになるみたいです。
また、特定の API に対して個別にCORSを許可する場合は、以下のように
api.use(cors(corsOptions));
で指定できます。
例えば、ローカルホストや開発環境だったら、どこからでも叩けるようにして、それ以外はCORSで弾くみたいな感じの条件分岐で記述していく感じ。
まとめ
- ブラウザでは、フロントエンドから違うドメインへのアクセスに対して、
CORS
がサーバ側で許可されている場合を除き、セキュリティ上の問題からアクセスできない場合が多い - このアクセスを許可するために、CORSを使う
- CORSとは、「
あるオリジンで動いている Web アプリに対して、別のオリジンのサーバーへのアクセスをオリジン間 HTTP リクエストによって許可できる仕組み、ルール
」のこと - 許可するためには、CORSを有効にする(HTTPレスポンスヘッダーに
Access-Control-Allow-Origin
等の実装)に加え、OPTIONSメソッド
の実装する必要がある - HTTPで使われるメソッドといえば、
GET
やPOST
が一般だが、一方、OPTIONSメソッドは「サーバーがどのメソッドをサポートしているかを調査するためのメソッド」。 - プリフライトリクエストを投げて、そこからOKであれば、
204
でレスポンスが返ってきて、そこからメインリクエスト(GETメソッド等)200
レスポンスが返ってくる。つまりレスポンスが204と200の2個返ってくる - CORSを許可するための実装には、
XHR
、fetch
、axios
、Express
を使う場合などがある
疑問
- Qiitaに書きながら思っていた謎
- 謎1:GETメソッドでもCORSで弾かれてた点
- → 調べてみると、GETメソッドでも弾かれるケースがあり、どうやら制限が厳しくなっているっぽい
- 謎2:OPTONSメソッドとヘッダーを加えないといけないが、GERAのソースにはOPTONSメソッドしか書いていなかくても正しくAPIが返ってきている点
- → どちらかだけ書いてればOKになったみたい(ここは制限がゆるくなった?)
- 謎1:GETメソッドでもCORSで弾かれてた点
参考
- オリジン間リソース共有 (CORS) - HTTP | MDN
- なんとなく CORS がわかる...はもう終わりにする。