Serverless FrameworkでCORSに対応しつつ、認証にCustom Authorizerを使ってみる

AWS運用自動化サービス「Cloud Automator」

業務改善の中でChromeExtensionを作成中に、API GatewayのCORS対応をしたのでその内容をご紹介します。

CORSとは?

CORSとはCross Origin Resource Sharingのことで、とあるWebアプリケーションから、そのアプリケーションとは異なるドメインにリクエストを行うときの取り決めです。
今回は、ユーザーが表示しているWebページ(google.com)から自分が作成したAPI(xxx.execute-api.ap-northeast-1.amazonaws.com)を叩く、というようなChromeExtensionを開発していました。
このケースの場合、WebページのドメインとAPIのドメインは異なるのでCORS対応を行わない場合には、ブラウザによってレスポンスが捨てられます。

こんなときは、アクセスされる側(今回のケースではAPI Gateway)からレスポンスを返す時に、Headerに Access-Control-Allow-Origin や、Access-Control-Allow-Headers を含めることで「このドメインからの、このヘッダーを含むリクエストなら受けてもOK」を示すことができ、これによりブラウザとAPI間で正しい通信ができるようになります。

Custom Authorizerとは?

Custom Authorizerとは、AWSのAPI Gatewayに用意されている認証・認可の仕組みです。API Gatewayのリクエストの処理が行われる前に、CognitoまたはLambdaによる認証・認可の処理を実装することができます。今回はLambdaで実装しました。

本題

というわけで、今回はServerless Frameworkを使った時のCORSとCustom Authorizerの実装方法のサンプルです。
リポジトリは以下です。

https://github.com/galactic1969/serverless-auth-cors-sample

serverless.ymlの抜粋を記載します。

CORS対応については、 cors: 以下にorigin(Access-Controll-Allow-Origin)とheader(Access-Controll-Allow-Headers)を書けばOKです。
なお、CORS対応を行うと、API Gatewayのリソースにpreflight用のOPTIONSメソッドが実装されます。これはMOCKで実装されているため、OPTIONSメソッド用のLambdaがデプロイされるようなことはありません。

Custom Authorizerについては、Functionにauthorizerの設定を記載し、別途authorizer用のFunctionもserverless.ymlに追加します。

今回のサンプルでは、IP制限とアクセスキーの2つの要素で認証を実装してみました。なお、source_ipを代入するところで、 event['headers']['X-Forwarded-For'].split(',')[0].strip() としているのは、X-Forwarded-For にはClientのIPアドレスと、CloudFrontのIPアドレスの2つが入っているためです。

APIのテスト

※このテスト(リポジトリ)では access-control-allow-origin が * となっていますが、実際は環境に応じて適切なoriginに書き換えてください

まずはGET。問題ありません。

次にPOST。ちゃんと返ってきてます。GETと違って、access-control-allow-headers にContent-Typeが入っています。(これは、Lambdaからレスポンスを返す時に指定しています。)

最後に、OPTIONSです。Webアプリケーションから行うリクエストの内容によっては、preflight requestというものをOPTIONSメソッドで直前に投げ、期待する結果が返れば本来のリクエストが投げる、という手順を踏む必要があります。preflight requestの詳細については、 こちら をご確認ください。

最後に、認証失敗のケースです。ちゃんとレスポンスヘッダーに「access-control-allow-origin」と「access-control-allow-headers」が入っていることが確認できます。 これは、 apigw.yml にてAPI Gatewayのレスポンスタイプを定義しているため、これらのヘッダーがレスポンスに含まれています。レスポンスタイプを定義しない場合、400系や500系のエラーの際にレスポンスにCORS系のヘッダーが入らず、クライアント側での切り分けが難しくなります。(サーバー側は400系や500系を返しているが、CORSに準拠していないレスポンスをブラウザ側が受け取る際に拒否してしまうため)

まとめ

というわけで、Serverless FrameworkでCORSに対応しつつ、認証にCustom Authorizerを使ってみる話でした。
Serverless Frameworkはプラグインや設定が多く中々使いこなすのが大変ですが、その分いろいろな事ができるので便利ですね!
今後も色々な設定を試していってみたいと思います。

AWS運用自動化サービス「Cloud Automator」