Cloud MapとECSを使ったAPI Gatewayでのpreflightリクエスト対応を行う

2021.07.07

はじめに

データアナリティクス事業本部のkobayashiです。

ECSで構築したサービスをAPI GatewayのHTTP APIとCloud Mapのプライベート統合で呼び出す仕組みを作っています。その中でAPI GatewayへのアクセスをCognitoで認証したユーザーだけが使えるという制限をかけようとしていたところ、API Gatewayの設定でハマってしまったのでその内容と解決方法をまとめます。

構成図

Cognitoで認証されたユーザーがCognitoで発行されたIdTokenのJWT認証を使ってAPI Gatewayを呼び出します。API GatewayのHTTP APIへ来たリクエストについてはCloud Mapを使ってECSへ流すという仕組みです。またこのAPIはウェブアプリからaxiosで呼び出すことを想定していました。

行った作業とエラーにハマるまで

上記のAPI GatewayとECSやAPI GatewayとCognitoの連携といった仕組自体はAWSアーキテクチャブログや弊社でのブログでも紹介されているように極一般的な仕組みかと思います。

従いまして、これらの記事を参考に以下の作業を行いました。

  1. ECS,Cloud Map,API Gatewayで認可なしのAPIエンドポイントを作成する
  2. API Gatewayの認可にCognitoを設定する
  3. curlコマンドでAPI Gatewayのエンドポイントにリクエストを送る
  4. ウェブアプリからaxiosでエンドポイントにリクエストを送る

大体想像はつくかと思いますが1-3は特に問題もなく順調に作業を行えましたが、4のaxiosを使った箇所でハマってしまいました。よくaxiosのリクエストの仕様を考えれば簡単に解決できると思うのですが少々手間取ってしまいました。

問題を解決する前のAPI Gatewayの設定

はじめに問題を解決する前のAPI Gatewayの設定を確認します。

API Gatewayの設定

API Gatewayで行った設定は先に紹介したAWSアーキテクチャブログ(Field Notes: Integrating HTTP APIs with AWS Cloud Map and Amazon ECS Services | AWS Architecture Blog )の内容を踏襲して構築したのでAPI Gatewayの中身を見ると以下のようになっています。

ルートは以下のように$defalutがだけがあります。

また統合については以下のような形になっています。

これらの設定では任意のメソッド、任意の宛先へのリクエストをECS上のサービスで受け取りレスポンスを返すことができます。

API Gatewayの認可でCognitoを使う設定

あらゆるユーザーがエンドポイントへアクセスしてAPIを使うことができてしまうのでCognitoで認証されたユーザーだけがAPIを使えるように設定を行いました。これは弊社ブログにそのままのエントリー(Cognitoで認証されたユーザーだけがAPI Gatewayを呼び出せるオーソライザーを使ってみた | DevelopersIO )がありますのでこちらを踏襲しています。

Cognitoでの設定をAPI Gatewayでの設定を行うとAPI Gatewayの認可については以下のようになっています。

エンドポイントへのアクセス(curlコマンド)

これでECSで構築したAPIサービスをAPI Gatewayを呼び出す際にCognitoで認証されたユーザーだけがこのAPIを使えるようになります。 試しにcurlでAPIを叩いてみると問題なくレスポンスが返ってきました。

curl -H GET 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/' -H 'Content-Type:application/json;charset=utf-8' -H 'Authorization: Bearer {IdToken}'
HTTP/1.1 200 OK
Date: Mon, 14 Jun 2021 01:57:29 GMT
Content-Type: application/json
Content-Length: 107
Connection: keep-alive
server: uvicorn
apigw-requestid: A5GddjycNjMEJrA=

{
  "success": true,
  "message": "成功です"
}

Response code: 200 (OK); Time: 151ms; Content length: 87 bytes

prefligthによる認証エラー

curlコマンドでAPI Gatewayへのエンドポイントのリクエストに問題がなかったため、次にウェブアプリからaxiosを使って以下のようなコードでリクエストを送るとエラーとなってしまいました。

import axios from "axios";

export const getData = async () => {
    try {
        const res = await axios.get(
            "https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/",
            {
                headers: {
                    Authorization: 'Bearer {IdToken}',
                },
            }
        );
        console.log(res.data);
    } catch (error) {
        console.log(error);
    }
};

エラー内容を確認してみるとレスポンスとしては以下が返ってきました。

Access to XMLHttpRequest at 'https://xxxx.execute-api.ap-northeast-1.amazonaws.com/param' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

またAP Gatewayのログを見ると以下のエラーが記録されていました。

{
    "httpMethod": "OPTIONS",
    "ip": "xxx.xxx.xxx.xxx",
    "protocol": "HTTP/1.1",
    "requestId": "ApWmCgCytjMEJ1w=",
    "requestTime": "09/Jun/2021:07:17:07 +0000",
    "responseLength": "26",
    "routeKey": "$default",
    "status": "401"
}

このエラー内容を見れば分かる通りaxiosでクロスドメインのリソースに対しリクエストを送った際のpreflightの考慮漏れでした。

preflightリクエストは実際のリクエストを送る前にOPTIONSリクエストを送るのですが、ここまでの作業でのAPI Gatewayの設定ではリクエストの種類に関わらずJWT認証を行っている$defaultルートを経由しているため、preflightのOPTIONSリクエストではこのJWT認証を回避する必要があリます。

API Gatewayのpreflight対応を行う

対応方法は非常に簡単でAWSのドキュメントにも記載されていますので以下の内容に従ってAPI Gatewayの設定を修正します。

OPTIONS /{proxy+}ルートの追加

OPTIONSリクエストの場合は認証をしないルートを作成します。

手順1) API GatewayのルートでCreateを押下し、OPTIONSを選択し/{proxy+}を入力してルートの作成をする。

ルートは以下のように$defalutOPTIONS /{proxy+}ルートができます。

手順2) 追加したOPTIONS /{proxy+}ルートに$defalutと同じ統合をアタッチする。

手順3) CORSの設定を行う。

今回の設定値はゆるい設定をしてあるので実際にはAccess-Control-Allow-Originを適切な値にするなど環境に合わせて設定してください。

認可は$defalutルートにはJWT認証、OPTIONS /{proxy+}はオーソライザーをアタッチしないため、改めての設定は特にありません。

これでAPI Gatewayのpreflight対応が終わりましたので再度ウェブアプリからaxiosを使ってリクエストを送ると正常にcurlコマンドと同じレスポンスが返ってきました。

まとめ

API Gateway + Cloud Map + ECSでのpreflightリクエスト対応を行いました。それぞれのパートでの情報はたくさんありましたがまとまった情報が見つからなかったのでまとめてみました。どなたかのお役に立てれば幸いです。

最後まで読んで頂いてありがとうございました。