APIGatewayとS3＋CloudFront間のCORS問題の解決方法

# APIGatewayとS3＋CloudFront間のCORS問題の解決方法

WEBアプリを開発していると、CORS(Cross-Origin Resource Sharing)の問題で詰む人も多いのでは？
私もかなりハマったので、私の環境で解決した方法をメモとして残しておきます。

## CORSってなに？
ブラウザが `https://aaaa.com/` というサイトを表示中に `https://bbbb.com/`というサイトへのアクセスを制限するというもの。

よく使われる例としては、ブラウザがあるWEBページを表示する際に、APIで情報をとってくるパターン。このWEBページのドメインとAPIのドメインが異なる場合、CORSのでエラーとなってしまいます。

とまぁCORSの説明をしていると日が暮れるので、CORSに関してざっくりでも内容を知っている方向けに対応策を書いていきます。

参考：https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

## 構成
上記で例として上げた**ドメインの異なるWebページとAPIの接続**をAWS上で実現したいです。WEBページからWebAPIリクエストをして、Web画面上で表示する流れです。

* WEBページ → S3＋CloudFront＋React
* API → APIGateway＋PHP

## 対応した設定内容
CORSの対応ではHTTPヘッダーのやりとりで解消するので、その設定が必要となります。設定する内容は下記の4つ。

1. APIGateway
2. PHPでの返却値対応
3. S3
4. CloudFront

利用している各種AWSサービスでそれぞれCORSの設定かつPHP側でのソースレベルでの修正が必要でした。**私の環境ではこの1と2の設定が重要でした。**

## 手順1, APIGatewayの設定
GETメソッドを例に設定項目を見ていきます。
### 手順1-1, OPTIONメソッドの設定
まずはOPTIONで「メソッドレスポンス」を選択。

<img width="1178" alt="1.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/2bf35869-06f2-b886-17a1-663ec3a36af9.jpeg">


ブラウザに返却するヘッダーを設定します。

```
Access-Control-Allow-Headers
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
```

<img width="1180" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/f66f9e5a-a188-fd40-6d97-e1b91b6828a9.png">


次に同じくOPTIONの「統合レスポンス」で返却するヘッダーにセットする値を設定します。

```
Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Amz-Security-Token,X-PINGOTHER,Access-Control-Allow-Origin'
Access-Control-Allow-Origin:'https://example.com'
Access-Control-Allow-Credentials: 'true'
Access-Control-Allow-Methods: 'GET,OPTIONS'
```
<img width="1178" alt="3-1.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/707d10c8-2f99-4358-08e7-cf90c877b9f5.jpeg">

<img width="1180" alt="3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/b408ec22-309b-23a2-3e8f-a0e5bf8db166.png">


ここで注意点が二点あります。
1. `Access-Control-Allow-Origin`には呼び出し元のドメイン（サブドメインの場合も正確に）
2. `Access-Control-Allow-Credentials: 'true'`を必ずセット

私が参考にしたサイトでは `Access-Control-Allow-Origin:*`とセットされていたり、`Access-Control-Allow-Credentials`は言及されていないことが多かったですが、私の場合はこの2点がキモでした。


### 手順1-2, GETメソッドの設定
次はGETにメソッドを設定していきます。

<img width="1171" alt="4.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/4801d81f-5537-263f-55ff-f5a03925f469.jpeg">

今回はVPCリンクを使っており、「統合リクエスト」で**プロキシ統合の使用**にチェックを入れていました。

<img width="1182" alt="5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/a58f3cc6-aec7-c6dd-a502-c47789ad9f6f.png">

プロキシ統合に関しては下記の記事を参考にしてください。
（参考： [API Gateway + Lambda プロキシ結合の使用有無による違い](https://qiita.com/yuuwatanabe/items/a3bd65e709f20574b6db)）

**プロキシ統合の使用**っていうのはいわば「API Gateway側でうまく仕事してくれるよ」ってことなんですが、これによって「統合レスポンス」の設定ができなくなるんです。

<img width="1174" alt="6.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/fee95e36-3700-23a0-ccf2-e32b13bb04c2.jpeg">

私はここでハマりました。
プロキシ統合を利用して、APIGateway側でよしなにしてほしい。
＝プログラム側では制御できない。

だと思いこんでいましたが、プロキシ統合を利用してもプログラム側でヘッダー情報の付与してもいいみたいでした。

## 手順2, プログラム側での対応
APIGatewayで設定できないHTTPヘッダーの値をPHP側で設定してあげます。今回はFuelPHPで書いています。

```php
$ret->set_header('Access-Control-Allow-Origin', 'https://example.com');
$ret->set_header('Access-Control-Allow-Credentials', 'true');
```

これだけでOKです。

要は、手順1-1で行った統合レスポンスの設定をプログラム側でセットしようということです。

そういう意味では`Access-Control-Allow-Headers`と`Access-Control-Allow-Methods`も設定せねばならなそうですが、今の所はこれで動いています。

今回はVPCリンクで使用していますが、LamdaでもHTTPでも同様の設定でいけると思います。もちろんアプリケーション側もNode.jsなどでもOKです。

## 手順3, S3の設定
ここまでくればあとは流れ作業です！

S３にもCORSの設定を行います。
「S3バケット＞アクセス権限＞CORSの設定」に下記をそのままコピペでOKです。

`AllowOrigin`はこれまでと同じように許可すべきドメインに変更してください。

```html

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>https://example.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
```

<img width="1324" alt="7.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/6c98625b-b051-f3c7-15e4-9df38a92f8a7.jpeg">


## 手順4, CloudFrontの設定
最後にCloudFrontです。

「Distribution > Behaviors > Edit」から設定をします。

* 「Cache Based on Selected Request Headers」を`White List`に設定
* 「Whitelist Headers」で`Origin`を追加

<img width="926" alt="8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562723/86cdb038-6a10-1d2a-5264-29edb3a2aeb4.png">


## まとめ
CORSの解決はAWS側の設定だけで大丈夫だという先入観があることでハマってしまった事例かと思います。

自前で設定してあげるというのは少し考えれば至極当然なのですが、、、

CORSはWebを触っていると一度はぶつかる壁だと思います。
僕と同様困っている人の手助けになれば幸いです。