【実践編】CORS ～AWS100本ノック～ 17/100

# はじめに

前回は、[【概要編】CORS ～AWS100本ノック～ 14/100](https://qiita.com/namasa/items/53668313fec5ddf9a0c9)という記事でCORSの概要について説明しました。
今回は、実際に試しながら挙動を確認していきます。

以下のようなAWSの構成で試していきます。
![CORS-AWS.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/23a9c0ce-7116-5e22-454d-175deb958885.png)

:::note info
S3やAPI Gatewayについては適宜、IP制限などを行ってください
:::

各AWSリソースについて簡単に紹介します。

## 1. バックエンドのLambda関数を作成
以下のコードを配置したLambda関数を作成します。

```js:index.js
export const handler = async (event) => {
    console.log(event);
    const response = {
        statusCode: 200,
        headers: {
            "Test-Header": "xxx"
        },
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};
```

## 2. バックエンド用のAPI Gatewayを作成
HTTP APIを選択します。
ルートとしては`users`を作成し、メソッドとしては`GET`と`POST`を設定します。
Lambda統合を選択し、1.で作成したLambda関数に紐づけます。

## 3. 画面表示用のS3バケットの作成
S3バケットを作成します。
jsファイルの`url`部分を作成したAPIのURLに変更した以下のhtmlファイルとjsファイルをアップロードします。
S3バケットに静的ウェブサイトホスティングを設定し、インデックスドキュメントに`index.html`を設定します。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="icon" href="data:,">
    <title>ファイルアップロード</title>
</head>
<img src="https://placehold.jp/300x200.png">
<body>
    <h1>GETリクエスト</h1>
    <button onclick="getRequest()">実行</button>
    <script src="getRequest.js"></script>
    <h1>POSTリクエスト</h1>
    <button onclick="postRequest()">実行</button>
    <script src="postRequest.js"></script>
</body>
</html>
```

```js:getRequest.js
async function getRequest() {
    const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'; // xxxxxの部分はAPI GatewayでAPIをデプロイ時に発行されるユニークなIDです
    const response = await fetch(url, {
        method: 'GET'
    });
}
```

```js:postRequest.js
async function postRequest() {
    const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'; // xxxxxの部分はAPI GatewayでAPIをデプロイ時に発行されるユニークなIDです
    const response = await fetch(url, {
        method: 'POST'
    });
}
```

ここまでやったら、ウェブサイトホスティングで払い出されたURLにアクセスしてみましょう。
以下のような画面が表示されれば準備OKです :smiley: 
![スクリーンショット 2024-12-05 172535.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/0ff8a864-2374-3346-670b-90bcd711a31f.png)




# 試してみる
では、実際に試していきます。

## SimpleRequest, PrefrightRequestの挙動確認

### 同一オリジンポリシーの対象にならないリクエスト
[こちら](https://qiita.com/namasa/items/53668313fec5ddf9a0c9#%E5%90%8C%E4%B8%80%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%B3%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%E3%81%AE%E5%88%B6%E9%99%90%E3%81%AE%E5%AF%BE%E8%B1%A1%E3%81%AB%E3%81%AA%E3%82%8B%E3%82%82%E3%81%AE%E3%81%AA%E3%82%89%E3%81%AA%E3%81%84%E3%82%82%E3%81%AE)で紹介した通り、一部のHTMLタグなどは同一オリジンポリシーの対象になりません。

別オリジンの画像が表示されていることからもわかるように、`<img>`タグは同一オリジンの対象になっていないことが確認できます:v:

### SimpleRequestになるリクエスト

[こちら](https://qiita.com/namasa/items/53668313fec5ddf9a0c9#simplerequest%E3%81%A8prefrightrequest)に記載した通り、SimpleRequestになる条件があります。

GETリクエストを実行してみましょう。
usersに関して本リクエストしか飛んでいないため、SimpleRequestであることがわかります。

![スクリーンショット 2024-12-05 204445.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/19da05d6-321b-29ab-8ab7-f643c3b1eed3.png)



### PrefrightRequestが飛ぶリクエスト

次にgetRequest.jsを修正してカスタムヘッダーとして、`Content-Type: application/json`を付けて実行してみましょう。
```js:getRequest.js
async function getRequest() {
    const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users';
    const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json'
        }
    });
}
```

![スクリーンショット 2024-12-05 204236.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/8f940020-3c23-b985-5c9c-0a73f59311f4.png)

SimpleRequestの条件から外れたため、usersの`プリフライトリクエスト`が飛んでいることがわかります。

## ヘッダー設定による挙動の確認

### 全許可

API GatewayのCORS設定を修正し、`Access-Control-Allow-Origin`、`Access-Control-Allow-Headers`、`Access-Control-Expose-Headers`を`*`にして全てを許可します。
![スクリーンショット 2024-12-05 204847.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/69e94347-435b-a56f-8617-62da023c186d.png)

この状態であれば、どこからでもアクセスできるし、どのヘッダーもリクエストできるし、どのレスポンスヘッダーもjsから取得できます。

getRequest.jsを少し変えて
リクエストに`Custom-Header`を追加し、Lambdaのレスポンスヘッダーの`Test-Header`の値を取得して表示するようにします。
```js:getRequest.js
async function getRequest() {
    const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users';
    const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Custom-Header': 'xxx'
        }
    });
    console.log(response.headers.get('Test-Header'));
}
```


リクエストすると、`Custom-Header`がリクエストでき、レスポンスヘッダーの`Test-Header`の値が取得出来ています。
![スクリーンショット 2024-12-05 205705.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/5d173c91-23c0-335a-d839-c73304aa89c3.png)

![スクリーンショット 2024-12-05 210126.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/c1c06e7d-942e-5e85-aa67-64e781e201ba.png)

### 許可していないOriginからアクセス

`Access-Control-Allow-Origin`を修正し、Originが`https://example.com`からのリクエストのみ許可してみましょう。
![スクリーンショット 2024-12-05 210321.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/0e01528d-89e7-941b-c0fc-a52ce753bbbe.png)

リクエストすると、CORSエラーになっていて本リクエストは実行されていないことがわかります。

![スクリーンショット 2024-12-05 210603.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/d82d3b9d-a32d-1f08-0769-869eb91b4c4e.png)

コンソールを見ると以下のような許可していないオリジンからアクセスしている旨のエラーメッセージが表示されています。
```sh
Access to fetch at 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'
from origin 'http://static-hosting-cors-client.s3-website-ap-northeast-1.amazonaws.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```
![スクリーンショット 2024-12-05 210808.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/024aa130-92b9-4271-4c22-de9810f0c244.png)

### 許可していないAccess-Control-Request-Headers

`Access-Control-Allow-Headers`を修正し、Content-Typeのみ許可し、Custom-Headerは許可しないでみましょう。

![スクリーンショット 2024-12-05 211158.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/cb9228cd-4e37-bb56-5d1f-96afc7558af7.png)

リクエストすると、CORSエラーになります。
コンソールを見ると先ほどと同じメッセージですね :thinking: 
どうやら何が原因かはわからないように隠蔽されているようです。(セキュリティ対策？)

```sh
Access to fetch at 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users' 
from origin 'http://static-hosting-cors-client.s3-website-ap-northeast-1.amazonaws.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```

### 許可していないAccess-Control-Request-Method

`Access-Control-Allow-Methods`を修正し、POSTとOPTIONSのみ許可にしましょう。

![スクリーンショット 2024-12-05 211944.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/00091819-0050-69c9-728f-df3a4db9fdee.png)

リクエストすると、これまでと同様CORSエラーになります。
※エラーメッセージは先ほどと同じなので省略

### 許可していないAccess-Control-Expose-Header

`Access-Control-Expose-Headers`を修正し、未設定にしましょう。

![スクリーンショット 2024-12-05 212209.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/6b6d66dc-d618-8890-666b-77b50af42d1c.png)

リクエストすると、正常にリクエストできます。
しかし、コンソールを見るとjsからレスポンスヘッダー`Test-Header`の値が取得できずにnullになっていることがわかります。

![スクリーンショット 2024-12-05 212409.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/22b1d7b6-f882-9314-1e48-c15fa5830470.png)


![スクリーンショット 2024-12-05 212435.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/cc030435-9c72-e1f9-e2ae-15731450903c.png)

### Access-Control-Allow-Credentialsをfalseで資格情報をリクエスト

getRequest.jsを修正し、資格情報を送るようにしてみましょう。

```js:getRequest.js
async function getRequest() {
    const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users';
    const response = await fetch(url, {
        method: 'GET',
        credentials: 'include',
        headers: {
          'Content-Type': 'application/json',
          'Custom-Header': 'xxx'
        }
    });
    console.log(response.headers.get('Test-Header'));
}
```

![スクリーンショット 2024-12-05 212650.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/4bcdb4b7-052b-25a3-1eb9-b42ff864ba61.png)

リクエストをすると、`Access-Control-Allow-Origin`が`*`のリソースに対しては資格情報を送ることが出来ないエラーになります。

![スクリーンショット 2024-12-05 212928.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/0165c937-bd57-e880-bbf0-e2e6475f40c1.png)


`Access-Control-Allow-Origin`を指定して再実行してみます。
![スクリーンショット 2024-12-05 213158.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/3da66715-7a90-5cd7-6262-46ceff66a8f1.png)

しかし、同じエラーになります :cry: 
エラーメッセージではわかりませんが資格情報を送る際は、`Access-Control-Allow-Headers`も`*`を利用できません。

`Access-Control-Allow-Headers`を指定して再実行してみます。
![スクリーンショット 2024-12-05 213437.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/19a9ef63-3845-3c56-7462-457b0d45198f.png)

今度こそリクエストでき...ませんでした :cry: 
エラーメッセージを見ると、`Access-Control-Allow-Credentials`が許可されていないメッセージが表示されます。
![スクリーンショット 2024-12-05 213609.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/5d1200d8-1371-9aa1-ef5c-2dec37904527.png)

`Access-Control-Allow-Credentials`をtrueにしてリクエストしてみます。
![スクリーンショット 2024-12-05 213743.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/91efd237-ef68-30b4-5d4e-134d242e653b.png)

リクエスト出来ました :smile: 

![スクリーンショット 2024-12-05 213900.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/5f4db966-899c-1aaf-4fbb-d4f06e7dee52.png)


### Access-Control-Max-Ageが0

今までずっとAccess-Control-Max-Ageの設定を0にしていましたが、この設定だと常にプリフライトリクエストが飛ぶことになります。
何回か実行してみると、毎回飛んでいることがわかります。
![スクリーンショット 2024-12-05 214117.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/9ca086a4-cd06-78bd-f3df-7615970da146.png)


### Access-Control-Max-Ageの期限内or期限外

毎回プリフライトリクエストを飛ばすのも過剰ではあると思うので、試しに30秒に設定してみましょう。
![スクリーンショット 2024-12-05 214340.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/a089e587-a6b0-301a-1cb6-9564e9f763ca.png)

試してみると、最初の1回目はプリフライトリクエストが飛びますが、それ以降は30秒経過するまでプリフライトリクエストが飛びませんでした。
![スクリーンショット 2024-12-05 215002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/6d199401-3fb4-a942-f6d0-33c592dc4f81.png)

## おまけ
AWSを用いたWebアプリケーションでCORS設定をする場合、それぞれどのサービスで設定するのでしょうか？
簡単に紹介します。

- CloudFrontとS3で設定する
- API Gatewayで設定する
- バックエンドで設定する(Laravelならconfig/cors.php)


# おわりに
今回はCORSについて、S3、API Gateway、Lambdaを利用して実際に画面からAPIを叩きながら説明しました。
前回の記事と併せて、CORSの全体像が分かっていただければうれしいです:smile: