CloudFront と API Gateway で SPA の CORS 問題をイイ感じに解決する

87件のシェア(ちょっぴり話題の記事)

渡辺です。 弊社ではお客様の悩みや問題を解決するアンサーブログという文化がありますが、新しく「ドキュメントはブログ」というのを試しています。

現在、 Developers.IO Cafe はSPA(Single Page Application)で構成されています。 SPAとは、単一のウェブページ上でJavaScriptによるルーティングの処理を行うWebアプリケーションです。 一般的に、SPAで内のコンテンツは、APIを通して取得します。 この時、悩ましいのが CORS(Cross-Origin Resource Sharing) です。

本エントリーでは、カフェのSPAでとったCORS対策について解説します。

CORSとは?

CORSとは、簡単に言うと、 ウェブサイトが異なるドメインに対するAPIリクエストをブロック する仕組みです。 あるウェブサイトを開いている時、まったく関係ない別のドメインに対し、入力した情報を送信されることはセキュリティ的に望ましくありません。 このため、モダンなブラウザでは クロスドメインのAPI実行が出来ない ようになっています。 例えば、ECサイトがハッキングされ、クレジットカードやログインパスワードを別のサイトに送信される状態になっていたら危険でしょう。

クロスドメインでAPIを実行したい場合、 API側で明示的なCORSを許可する設定が必要 となります。 詳細は割愛しますが、「このAPIは、このドメインからアクセスされることを想定している」という情報をブラウザに返す必要があります。

API GatewayのCORS設定

API Gatewayを利用してAPIを構築している場合、API GatewayのCORS設定機能が便利です。 しかし、API GatewayのバックエンドにLambdaを利用しており、かつ Lambda Proxyで利用している場合、API GatewayのCORS設定は利用できません。

現実問題として、API Gateway バックエンドのLambdaでは、Lambda Proxyを利用せざるを得ません。 カフェのAPIでも、必然的にLambda Proxyを利用しています。

Lambda Proxyを利用している場合、CORS設定はすべてカスタム実装しなければなりません。 具体的には、OPTIONSメソッドを定義し、該当するAPIのメソッドでAccess-Control-Allow-Headersを追加します(Amazon API Gateway をクロスオリジンで呼び出す)。 かなり面倒で、心が折れてきます。

それならば、Lamba Proxy統合を辞めて、Lambdaバックエンドにすれば「CORS有効」で終わるのですが、必要な情報が失われてしまうため、変更もできません。

クロスドメイン通信を回避する

CORS設定が必要なのは クロスドメイン通信 を行うからです。 ドメインが、サブドメインも含めて、完全に一致する のであれば、CORS問題から解放されます。 具体的には、Cloud Front + API Gatewayで次のような構成とします。

Cloud FrontのオリジンとしてS3バケットを置くのは、SPAではよくある構成です。 さらに、追加でAPI Gatewayのエンドポイントをカスタムオリジンとして設定します。 この時、パスでルーティングを制御する必要があるため、 /api/* をAPIのパスとしました。

CloudFrontのBehiver設定で、 /api/* のリクエストはAPI Gatewayへ、それ以外のパスはS3オリジンにアクセスさせます。

注意点ですが、ClouFrontでルーティングした時、リクエストのパスは変更できません(Lambda@Edgeを使えばできるかも)。 したがって、API Gatewayでは、APIのプレフィックスとして /api が必要です。 これは、API Gatewayのカスタムドメインを利用すれば簡単に実現出来ます。

この構成をとれば、SPAのドメインと完全一致するドメインでAPIを投げることができるため、クロスドメイン自体を回避できます。

まとめ

CORS設定として、*で全ドメインを許可していませんか? CORS設定すればクロスドメインでアクセスすることは可能です。 しかし、CORS設定をするよりも、クロスドメイン通信を回避する方が、セキュリティ的にベターではないでしょうか?

Cloud FrontとAPI Gatewayを利用したSPAを検討しているならば、定番にしたいパターンですね。