Google API Gateway 使って認証付き API を実装する
GCP の API Gateway の機能提供するサービスは Apigee だけだと思っていたところ、API Gateway のサービスがベータで公開されていたので、試します。
やること
GCP の API Gateway を使って Firebase で認証が必要な API を実装します。
サンプルアプリの構成
サンプルでは下記の API が実装されています。
Next.js で実装したフロントエンドアプリは Cloud Run 経由で配信します。 このアプリから Firebase Authentication で認証し、API へのアクセスにはこの認証情報を使用します。
API サーバーも Cloud Run で動作しています。 この API へのアクセスはすべて API Gateway を経由するようにし、認証情報の検証は API Gateway で吸収します。
実装は GitHub で公開しています。
サンプル API の構成
今回は Go 言語を使って下記 3 つの API を実装します。
/unprotected
と /protected
では下記のレスポンスを返すようにしています。
[ { "id": 1, "title": "Sample book 1", "description": "This is first sample book" }, { "id": 2, "title": "Sample book 2", "description": "This is second sample book" }, { "id": 3, "title": "Sample book 3", "description": "This is third sample book" } ]
/mirror
では API Gateway が接続先の API に渡している下記の認証情報をそのまま返します。
{ "name": "**********", "picture": "https://**********", "iss": "https://securetoken.google.com/**********", "aud": "**********", "auth_time": 0000000000, "user_id": "**********", "sub": "**********", "iat": 0000000000, "exp": 0000000000, "email": "**********@**********", "email_verified": true, "firebase": { "identities": { "google.com": ["**********"], "email": ["**********@**********"] }, "sign_in_provider": "google.com" } }
ただし、この認証情報は API Gateway が接続先の API にアクセスするときに X-Apigateway-Api-Userinfo
ヘッダーに Base64 エンコードされた状態で渡されています。
したがって、API を実装しているアプリケーション側でデコードし、JSON としてパースする必要があります。
API Gateway のデプロイ
API 定義の作成
API Gateway にゲートウェイを作成するためには、Swagger 2.0 で API の定義を作成する必要があります。 試しに、Open API Specification 3.0 のフォーマットで作成したファイルを使ってみましたがアップロードできませんでした。
Firebase Authentication で API へのアクセスを制限するには、Swagger 2.0 の定義に対して、API Gateway の拡張構文を追記します。
認証方法を定義します。
securityDefinitions: firebase: authorizationUrl: "" flow: "implicit" type: "oauth2" ### Replace YOUR-PROJECT-ID with your project ID x-google-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID" x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com" x-google-audiences: "YOUR-PROJECT-ID"
そして、認証が必要なスコープに対して、security
を定義します。
すべての API に対して アクセス制限をするならルート要素として、特定の API のみの場合は path
や 特定の HTTP メソッド以下に記述します。
security: - firebase: []
API ゲートウェイの作成
作成するゲートウェイに必要な情報を設定しておきます。
PROJECT_ID=REPLACE_WITH_PROJECT API_ID=sample-api-id CONFIG_ID=samle-config-id GATEWAY_ID=samle-gateway-id GCP_REGION=asia-east1
まず、API(API Gateway 内のプロジェクトのような扱いのもの)を作成します。
gcloud beta api-gateway apis create $API_ID --project=$PROJECT_ID
次に、作成した API 定義をアップロードして、ゲートウェイの設定を作成します。
gcloud beta api-gateway api-configs create $CONFIG_ID \ --api=$API_ID \ --openapi-spec=openapi.yaml \ --project=$PROJECT_ID
そして、ゲートウェイを作成します。
gcloud beta api-gateway gateways create $GATEWAY_ID \ --api=$API_ID \ --api-config=$CONFIG_ID \ --location=$GCP_REGION \ --project=$PROJECT_ID
作成したゲートウェイの情報を確認します。
gcloud beta api-gateway gateways describe $GATEWAY_ID \ --location=$GCP_REGION \ --project=$PROJECT_ID
以下のように表示されます。
defaultHostname
が API Gateway にアクセスするためのエンドポイントのホスト名です。
apiConfig: projects/PROJECT_ID/locations/global/apis/API_ID/configs/CONFIG_ID createTime: '2021-01-05T14:12:16.021807663Z' defaultHostname: *********.de.gateway.dev displayName: GATEWAY_ID name: projects/PROJECT_ID/locations/GCP_REGION/gateways/GATEWAY_ID state: ACTIVE updateTime: '2021-01-05T14:14:00.175372465Z'
試しにアクセスして、レスポンスが変えることを確認します。
curl https://*********.de.gateway.dev
/
は API として定義していないので 404 が返ります。
(作成したアプリケーションは/
でフロントエンドアプリケーションを返すようにしています)
{ "code": 404, "message": "Request `GET /` is not defined by this API." }
一方、認証が必要な API にアクセスすると 401 を返します。
curl https://*********.de.gateway.dev/protected { "message": "Jwt is missing", "code": 401 }
当然ですが、認証が必要ない API は 200 を返します。
curl https://*********.de.gateway.dev/unprotected [ { "id": 1, "title": "Sample book 1", "description": "This is first sample book" }, { "id": 2, "title": "Sample book 2", "description": "This is second sample book" }, { "id": 3, "title": "Sample book 3", "description": "This is third sample book" } ]
クライアントアプリを使った動作確認
サンプルアプリでは Fireabase Authentication を使った認証と、バックエンドの各 API を呼び出せます。
認証していない状態では、/mirror
と/protected
にアクセスできません。
一方、認証している状態ではすべての API にアクセスできていることがわかります。
その他の動作確認
カスタムヘッダーを付加する
Swagger 2.0 の拡張できる内容は下記の URL にまとめられています。
よくある処理として、API Gateway で CORS ヘッダーを追加したりがあると思いますが、現時点ではできないようです。
前段に Cloud Load Balancing を配置した場合、Cloud Load Balancing 側でヘッダーを付加することはできます。
カスタムドメインを使用する
現時点(2021/01/18)ではカスタムドメインは使用できないようです。 ただ、Cloud Load Balancing を前段に置く形であればカスタムドメインを使用できるようです。
For the Beta release, custom domain names are not supported on GCP for API Gateway. If you want to customize the domain name for the Beta, you have to create a load balancer to use your custom domain name and then direct requests to the gateway.dev domain of your deployed API.
バックエンド API への API Gateway 以外からのアクセスを拒否する
API 定義から設定を作成するときに、--backend-auth-service-account
をつけることでサービスアカウントを指定できます。
実際に試したわけではありませんが、このサービスアカウントの権限を適切に設定することで、アクセス制御が実現できそうです。
まとめ
GCP の API Gateway を使ってバックエンド API に認証機能を付加することができました。 API の定義に対して設定を記述することで機能を付加できる点は、個人的にはわかりやすくて好みでした。
ベータ版ということもあって機能は少なかったり、実装されていない機能もあるようですが、Cloud Load Balancing を使うことである程度補完できそうです。