javascriptでAPI Gatwayにリクエスト送ったらエラーが出た！ ＞ それ、CORSを忘れていない？

# 要旨
本記事は「**CORSを知らない人がCORSの存在にたどり着くためのきっかけになること**」を目的として、
「**CORSを知らずにjavascriptからAPI-Gateway呼んだ時に高確率で遭遇する事象**」からCORSを説明します。
　⇒ 「javascript API Gateway エラー」みたいに事象ベースでググって本記事が出てくれるとよいなと思っています。

CORSの詳細はQiitaに沢山の素晴らしい記事があるため、本記事でCORSの存在を知った後の詳細理解は他記事を参照されるのがよいかと思います。

# CORSを知らずにjavascriptからAPI-Gateway呼んだときに遭遇する事象
#####想定ケース
- API-GatewayでGET、POSTのエンドポイントを作成する
- **API-GatewayでCORSの設定は行わない**
- 上記エンドポイントに対してjavascriptでGET/POSTする静的HTMLコンテンツを作成
- POSTはjsonデータをPOST（Content-type:application/json）
- コンテンツをS3などにホストしてブラウザで開く

#####予想される事象
- `GET`のリクエストのレスポンスは200(正常)だが、javascriptでエラーとなる
- `POST`リクエストが実行されずに`OPTIONS`メソッドでリクエストが実行されて403エラーレスポンスが返る
- ブラウザのデバッグツールを見ると、「Access-Control-Allow-Originがどーたら」みたいなエラーメッセージが出ている

**上記ケースのような事象が起きた場合は、API-GatewayでCORSを設定していないことに起因する可能性が高いです。**

# CORSとは何か？（超ざっくり）
Cross-Origin Resource Sharing の略で、日本語では「オリジン間リソース共有」**\*1**などと呼ばれてます

> **\*1**：オリジンをすごくざっくり説明すると、プロトコル＋ドメイン。
　例）https://s3-ap-northeast-1.amazonaws.com

通常ブラウザはセキュリティの都合上別オリジンに対するリクエストは許可されていません **\*2** が、
「信頼済みされているオリジンからのリクエスト」を例外的に許容する仕組みがCORSです。

> **\*2**：別オリジンからのリクエストを無条件に許可するとCSRFに対する脆弱性になります。
　例）http://hoge-bank.co.jp にログイン後、悪意あるサイト http://evil.co.jp を開く。
　　　http://evil.co.jp 開くと自動でjavascriptからhttp://hoge-bank.co.jp にDELETEリクエストが実行
　　　http://hoge-bank.co.jp でCookieの検証等の対策を講じていないとDELETEが実行される

別ドメインに対してリクエストを送る場合は、クライアントブラウザとサーバは以下の方法で「送信元オリジンが信頼済み」であることを検証します

- リクエストヘッダ`Origin`にオリジンの値をセット(ブラウザが自動的にセット)
- サーバは「信頼済みオリジン」をレスポンスの'Access-Control-Allow-Origin'ヘッダに記載して返す
- ブラウザは自身のオリジンが`Access-Control-Allow-Origin`に含まれているか確認
- 含まれていない場合は**クライアントはレスポンスを受け入れない**
- レスポンスに`Access-Control-Allow-Origin`ヘッダが無い場合も同様に受け入れない

サーバはリクエスト処理してレスポンスを返すため、レスポンスコードは200(正常)となりますが、
`Access-Control-Allow-Origin`ヘッダでオリジンが許容されていないとクライアントがレスポンスを受け入れないのでjavascriptはエラーとなります。

また「DELETEやPUT」「Content-type:application/json」「カスタムヘッダがついている」など、
いくつかの条件に該当するリクエストは、サーバ側がCORS対応済＆オリジンが信頼済みであることを検証するため、**ブラウザが本リクエスト実行前に事前確認リクエスト(preflightRequest)を送ります。**

事前確認リクエストは`OPTIONS`メソッドで行われて、レスポンスの`Access-Control-Allow-Origin`で信頼が確認できた場合のみ、ブラウザは本リクエストを送ります。
信頼が確認できない場合は、本リクエストは送られません（サーバ側の処理も実行されません）

# 結局何が原因でjavascriptでエラーが発生したのか？
#####`GET`の場合
- 別オリジンであるAPI-Gatewayに対してリクエストを送信
- API-Gatewayの`GET`のエンドポイントの処理が実行される
- API-GatewayでCORSの設定していないため、`Access-Control-Allow-Origin`ヘッダなしで200(正常)レスポンスが返る
- `Access-Control-Allow-Origin`でオリジンが許容されていないため、javascriptでエラーとなる

#####`POST`の場合
- 別オリジンであるAPI-Gatewayに対してリクエストを`POST`を送信しようとする
- Content-type:application/jsonを`POST`する前に、`OPTIONS`メソッドで事前確認リクエストを実行
- API-Gatewayで`OPTIONS`メソッドが定義されていないため403エラーのレスポンスが返される
- 事前確認リクエストで信頼が確認できないので、本リクエストの`POST`は実行されない

# 解決方法 → API-GatewayにCORSを設定する
API-Gatewayの**リソース**→**アクション**から**CORSを有効化**後、ステージにデプロイします。(OPTIONSメソッドも自動で作られます)
Lambdaでプロキシ統合リクエストをする場合はLambda側で`Access-Control-Allow-Origin`のヘッダ設定が必要なのでご注意ください。

CORS有効化の設定時、`Access-Control-Allow-Origin`はデフォルトで`*`(全許可)になっています。
オリジンの全許可は前述のとおりセキュリティ上好ましくないため、ちゃんとした用途で使う場合は
`Access-Control-Allow-Origin`に適切なドメインを設定しておくことをオススメします。
（サーバ側で特別な対策せずにオリジン全許可すると、CSRFなどに対する脆弱性の危険が大きくなります）

# まとめ
閉域網内のAPサーバ上のServeletによるWebアプリケーションのようなケースでは通常ドメインまたぎリクエストは発生しないので、CORSを意識することはあまりなかったかと思います。

一方、S3やAPI Gatewayなどの様々なPaaSを組み合わせたアプリケーションはドメインまたぎを前提とするので、CORSを意識しながら作っていく必要があります。

**CORSを不適切に設定すると脆弱性につながる危険性があるので、「よくわからないけどCORSの有効化ボタン押したら動いた！」ではなく、仕組みを理解することが重要かと思います。**

以上