CORSブロックというフラストレーション
AWS API GatewayとLambdaを使用して、新しいAPIをデプロイしたばかりだとしましょう。Postmanでは完璧に動作します。JSONはきれいに返され、ステータスコードは輝かしい 200 OK。お祝いの準備は万端です。しかし、https://example.comにあるReactやVueのフロントエンドから呼び出した瞬間、ブラウザによって拒絶されます。
コンソールには突然、赤文字の警告が表示されます:
Access to fetch at 'https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/resource' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
これは**Cross-Origin Resource Sharing (CORS)**が原因です。ブラウザ内の警備員のようなものだと考えてください。サーバーがHTTPヘッダーを通じて明示的に「許可証」を渡さない限り、ブラウザはフロントエンドがデータに触れることを許可しません。
ステップ 1:ネットワークタブを確認する
コードを修正し始める前に、ブラウザのデベロッパーツール(F12)を開き、ネットワークタブに移動してください。再度API呼び出しを実行すると、通常、2つの異なるリクエストが表示されます:
- OPTIONSリクエスト(プリフライト): ブラウザがサーバーに対し、「このドメインからPOSTリクエストを送信しても安全ですか?」と実質的に問い合わせています。
- 実際のリクエスト: これは実際のGETまたはPOST呼び出しです。サーバーがOPTIONSリクエストに青信号を出した場合にのみ実行されます。
OPTIONSリクエストが403や404で失敗したり、Access-Control-Allow-Originヘッダーが欠落していたりすると、2つ目のリクエストは決して実行されません。
ステップ 2:API GatewayコンソールでCORSを設定する
最初のステップは、API Gatewayに対してこれらのプリフライトリクエストに自動的に応答する方法を指示することです。
- AWSマネジメントコンソールを開き、API Gatewayに移動します。
- 対象のAPIを選択し、特定のリソース(例:
/resource)に移動します。 - アクションメニュー(または新しいUIバージョンの専用のCORSボタン)で、CORSを有効にするを選択します。
- クイックテストの場合はAccess-Control-Allow-Originを
'*'に設定しますが、本番環境のセキュリティのためには'https://example.com'を使用してください。 - メソッドリストで
OPTIONSが選択されていることを確認します。 - CORSを有効にして既存のCORSヘッダーを置換するをクリックします。
デプロイを忘れないでください。 これはエンジニアが犯す最大のミスです。コンソールでの変更は、アクション > APIをデプロイをクリックし、ステージ(prodやdevなど)を選択するまで反映されません。デプロイしない限り、APIは古い壊れた設定のまま動作し続けます。
ステップ 3:Lambdaプロキシ統合のヘッダーを修正する
Lambdaプロキシ統合を使用していますか?その場合、先ほどのコンソール設定はOPTIONSリクエストのみを修正します。Lambda関数からの実際のデータレスポンスには影響しません。
プロキシ統合の設定では、Lambdaがすべてを制御します。コードのレスポンスオブジェクト内で明示的にCORSヘッダーを返さない限り、ブラウザは依然としてアクセスをブロックします。AWSコンソールでどれだけボタンをクリックしても関係ありません。
例:Node.jsでの修正
exports.handler = async (event) => {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "https://example.com",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key"
},
body: JSON.stringify({ message: "It works!" }),
};
};
例:Pythonでの修正
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': 'https://example.com',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
},
'body': json.dumps({'message': 'Success!'})
}
ステップ 4:エラー状態の処理
厄介な罠が1つあります。Lambdaがクラッシュするとどうなるでしょうか?catchブロックが500エラーを返してもCORSヘッダーを忘れている場合、ブラウザは500エラーを隠し、代わりに「CORSエラー」を表示します。これでは、開発者はコード内のバグが本当の原因であるにもかかわらず、API Gatewayの設定を調べて迷走することになります。
常にロジックをtry/catchで囲み、エラーレスポンスにも同じAccess-Control-Allow-Originヘッダーが含まれるようにしてください。CognitoやAPIキーを使用している場合は、API Gatewayの「ゲートウェイのレスポンス」セクションも確認し、401や403エラーでCORSヘッダーが渡されるようにする必要があります。
cURLによる検証
テストのためにブラウザを更新し続けるのはやめましょう。ターミナルでcurlを使用して、サーバーが実際に何を返しているかを確認してください:
# プリフライト(OPTIONS)のテスト
curl -v -X OPTIONS https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/resource \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST"
# 以下の特定の行が出力にあるか確認してください:
# < Access-Control-Allow-Origin: https://example.com
まとめチェックリスト
- Postmanは嘘をつく: CORSを強制しないため、ブラウザの互換性テストとしては不十分です。
- プロキシ統合は手動: LambdaコードでGET/POST用のヘッダーを返す必要があります。
- デプロイが重要: デプロイしなければ修正されません。それだけです。
- エラーはCORSに擬態する: Lambdaが失敗した際、ヘッダーが欠落していると実際のステータスコードが隠されてしまいます。

