困っていたこと
ローカル開発環境で、ブラウザで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がそのリクエストを許可してレスポンスを返す設定、という理解で合ってると思います。。怪しいと思う方はこちらを。
実装!
以下2つの設定が必要です。
- API GatewayにCORSを設定する(SAMのtemplate.yamlにて)
- LambdaのレスポンスヘッダーにCORS許可の設定を書いてあげる
1. API GatewayにCORSを設定する(SAMのtemplate.yamlにて)
ドキュメントはこの辺を参照しました。→(AWS::Serverless::Api#cors)
template.yamlに記述していきますが、Globals
に書く方法とResources
に書く方法の2パターンあります。何が違うかというと、Globals
に書けば一度の宣言でプロジェクト内のリソースに継承されるそうです。(ドキュメント:AWS SAM テンプレートの Globals セクション)
1-1. Globals
に書く
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とは)
1-2. Globals
ではなくResources
のApi
に書く(REST API)
ここでもやはり、"'*'"
というシングルクォテーションとダブルクォテーションが必要な文法になっています。
以下が例です。
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許可の設定を書いてあげる
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リクエストも成功しました。
ちなみにローカルで確認するだけなら、ここまでの間、sam build
、sam deploy
はしなくても大丈夫です。
最後に
この記事はREST APIでのCORS設定ですが、HTTP APIでのやり方は別記事としてまとめました→「AWS SAMでHTTP APIを実装する。CORSも設定する。REST APIと微妙に違う!」