AWS SAMのCORSを設定する（REST APIバージョン）

## 困っていたこと
ローカル開発環境で、ブラウザでReactからAWS SAMのAPI Gatewayを叩きたかったのですが、CORSに行く手を阻まれておりました。

下記は、CORS未設定の状態でAPIを叩いたときにブラウザのコンソールに出るエラーメッセージです。
>Access to fetch at ‘http://127.0.0.1:3001/hello_world’ from origin ‘http://127.0.0.1:3000’ 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.

噛み砕くと、
`‘http://127.0.0.1:3001/hello_world’へのアクセスはCORSポリシーによってブロックされて、preflightリクエストはアクセスコントロールチェックをパスできなかったよ。だって、リクエストされているリソースのヘッダーにCORSを許可する記述がないんだもん。かくかくしかじか...`
的なことが書いてあるのだと思います。

ちなみにCORSとは、Cross-Origin Resource Sharingの略で、オリジンAのリソースを表示しているブラウザからリクエストを受けた別のオリジンBがそのリクエストを許可してレスポンスを返す設定、という理解で合ってると思います。。怪しいと思う方は[こちら](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)を。

## 実装！

以下２つの設定が必要です。

1. API GatewayにCORSを設定する（SAMのtemplate.yamlにて）
1. LambdaのレスポンスヘッダーにCORS許可の設定を書いてあげる

### 1. API GatewayにCORSを設定する（SAMのtemplate.yamlにて）

ドキュメントはこの辺を参照しました。→（[AWS::Serverless::Api#cors](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-cors)）


template.yamlに記述していきますが、`Globals`に書く方法と`Resources`に書く方法の２パターンあります。何が違うかというと、`Globals`に書けば一度の宣言でプロジェクト内のリソースに継承されるそうです。（[ドキュメント：AWS SAM テンプレートの Globals セクション](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html)）

#### 1-1. `Globals`に書く

```yaml:template.yaml
Globals:
  Function:
    Timeout: 30
  Api:
    Cors:
      AllowOrigin: "'*'"
      AllowCredentials: true
      AllowMethods: "'POST'"
      AllowHeaders: "'Content-Type,X-CSRF-TOKEN'"
```

`"'*'"`というシングルクォテーションとダブルクォテーションを両方使う書き方がマストみたいです。どちらかだけのクォテーションにしてしまうと`Error: AllowHeaders must be a quoted string (i.e. "'value'" is correct, but "value" is not).`てな具合に怒られます。

まだこれだけではCORSはきちんと設定できていませんが、試しに一旦動作確認してみると、こんなエラー文になります。

> Access to fetch at 'http://127.0.0.1:3003/student_event_logs' from origin 'http://127.0.0.1:3000' 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.

さっきと同じエラーに見えますが、`Response to preflight request doesn’t pass access control check`というpreflightに関するエラー文が消えました。

開発者ツールでNetworkを確認してみると、確かにpreflightは通っています。（[MDN: preflightとは](https://developer.mozilla.org/ja/docs/Glossary/Preflight_request)）

![スクリーンショット 2021-11-12 11.36.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645077/4515b6fb-a6b0-49ad-cf6b-7453f2a5fc8f.png)

#### 1-2. `Globals`ではなく`Resources`の`Api`に書く（REST API）

ここでもやはり、`"'*'"`というシングルクォテーションとダブルクォテーションが必要な文法になっています。
以下が例です。

```template.yaml
Globals:
  Function:
    Runtime: ruby2.7
    Timeout: 3

Resources:
  HelloWorldLogsApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: 'dev'
      Cors:
        AllowOrigin: "'http://127.0.0.1:3000'"
        AllowCredentials: true
        AllowMethods: "'POST'"
        AllowHeaders: "'Content-Type,X-CSRF-TOKEN'"

  HelloWorldLogsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world_logs/
      Handler: app.lambda_handler
      Runtime: ruby2.7
      Events:
        ApiEvent:
          Type: Api
          Properties:
            RestApiId: !Ref HelloWorldLogsApi
            Path: /hello_world_logs
            Method: post
```

### 2. LambdaのレスポンスヘッダーにCORS許可の設定を書いてあげる

```ruby:app.rb(Lambda)
require 'json'

def lambda_handler(event:, context:)
  {
    statusCode: 200,
    body: {
      message: "hello world",
    }.to_json,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Credentials": true,
      "Access-Control-Allow-Methods": "POST",
      "Access-Control-Allow-Headers": "Content-Type,X-CSRF-TOKEN",
    }
  }
end
```

template.yamlと違い、`"'*'"`とすると、エラーになります。

### 3. 動作確認

この通り、先ほどのOPTIONS（preflight）に続き、POSTリクエストも成功しました。
![スクリーンショット 2021-11-12 11.36.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645077/5e015f17-2084-2c09-96a4-3a8370219e5d.png)
ちなみにローカルで確認するだけなら、ここまでの間、`sam build`、`sam deploy`はしなくても大丈夫です。

## 最後に
この記事はREST APIでのCORS設定ですが、HTTP APIでのやり方は別記事としてまとめました→「[AWS SAMでHTTP APIを実装する。CORSも設定する。REST APIと微妙に違う！](https://tech.fusic.co.jp/posts/2021-12-10-http-api-cors/)」