【初心者向け】Google Cloud API Gatewayを使って、認証付きWeb APIを作成してみた

2021.12.22

MAD事業部の佐藤@札幌です。

この記事は クラスメソッド Google Cloud Advent Calendar 2021 22日目の記事です。Google Cloud ほとんど触ったことないのに勢いで社内のGoogle Cloudアドベントカレンダーに申し込んでしまいました。私自身、AWSではよく Amazon API Gateway を使って開発をしていますが、Google Cloudにも API GatewayというフルマネージドなAPIサービスが 2020年の9月にβ版としてリリースされていました。そこで今回は、API Gatewayと Cloud Functionsを使って認証付きWebAPIを作成してみたいと思います。

Google Cloud API Gatewayとは

主にサーバーレスバックエンド(Cloud Functions、Cloud Run、App Engine)向けのWeb APIを統合的に管理することができます。JWT認証やAPI Keyの認証をサポートし、またロギングやモニタリングなどの機能をフルマネージドで提供してくれるサービスです。内部ではEnvoyを基盤に構築されているみたいです。

Cloud Functionsにサンプルアプリケーションをデプロイする

まずはCloud Functionsに、API Gatewayからアクセスされるバックエンドを作成します。クイックスタートベースでデプロイしていきます。Google Cloudのコンソールにアクセスし、CloudShellを起動します。以降はシェルについては、CloudShell上で実行します。

GitHubからサンプルプロジェクトをダウンロードします

Google Cloudが用意しているNode.jsのサンプルプロジェクトをダウンロードして、プロジェクトに移動します。

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
cd nodejs-docs-samples/functions/helloworld

サンプルコードは、Hello World!とレスポンスするだけのシンプルなアプリケーションです。

exports.helloGET = (req, res) => {
  res.send('Hello World!');
};

Cloud Functionsにデプロイする

サンプルプロジェクトが用意できたので、Cloud Functionsにデプロイします。ランタイムは nodejs16 とします。

gcloud functions deploy helloGET \
--runtime nodejs16 --trigger-http --allow-unauthenticated

これだけです。簡単ですね。コンソールでも確認してみます。以下のようにデプロイされていることが確認できました。

API GatewayのAPIを作成する

次にAPI GatewayのAPIリソースを作成します。API Gatewayについても CloudShell上で作成していきます。

gcloud api-gateway apis create sample-api

API Gatewayのコンソールに移動して、APIが作成されていることを確認します。

デフォルトのサービスアカウントに必要なロールを紐付ける

APIが作成できたら、次にこれ以降の設定を行うためにサービスアカウントに必要なロールと権限を付与します。

gcloud iam service-accounts add-iam-policy-binding 00000000-compute@developer.gserviceaccount.com \
  --member user:xxx \
  --role roles/iam.serviceAccountUser

構成を作成する

API が作成できたら次にCloud Functionsにアクセスするための構成を作成していきます。Google Cloud API Gatewayの構成とは、OpenAPI(Swagger)のyamlファイルのことです。これをコンソールからアップロードします。一部Google Cloud用の特殊なアノテーション x-google-backend というものを付与する必要があります。これに対して、Cloud FunctionsのURLを指定します。このyamlファイルをプロジェクトフォルダに置いておきます。

swagger: '2.0'
info:
  title: sample-api
  description: Sample API on API Gateway with a Google Cloud Functions backend
  version: 1.0.0
schemes:
  - https
produces:
  - application/json
paths:
  /hello:
    get:
      summary: Greet a user
      operationId: hello
      x-google-backend:
        address: https://xxx.cloudfunctions.net/helloGET
      responses:
        '200':
          description: A successful response
          schema:
            type: string

構成をデプロイします。

gcloud api-gateway api-configs create sample-config \
  --api=sample-api --openapi-spec=openapi.yaml

ゲートウェイを作成する

次に今アップロードしたOpenAPIのyamlファイルをゲートウェイにデプロイします。ゲートウェイにデプロイすると外部に公開されるURLが払い出され、OpenAPIの定義にそったパスでルーティングが行われます。

gcloud api-gateway gateways create sample-gateway \
  --api=sample-api --api-config=sample-config --location asia-northeast1

構成とゲートウェイが作成できたら、コンソールで確認してみます。

構成タブでは、openapiの定義ファイルが出力されています。

ゲートウェイタブでは、ゲートウェイのURLが払い出されています。これが、外部公開されているURLとなります。

このURLで先程の hello パスにアクセスしてみます。Cloud Functionsが実行され、 Hello World! が表示されました。

APIの概要タブを見てみると、リクエストやエラー、レイテンシなどの情報がリアルタイムに表示されるダッシュボードが作られています。

ログについては、詳細タブから飛べるようになっていて、以下のようにアクセスログが表示されています。

API GatewayにAuth0の認証をつけてみる

次に、API Gatewayに対して認証をつけてみたいと思います。API Gatewayでは、JWT(JSON Web Token)による認証がサポートされているので、これを使って認証付きAPIを作成します。今回は、Auth0を使用したJWT認証を行います。ここから、Auth0にアカウントを作成して適当なテナントを作成していることを前提に、Auth0のコンソールにログインして設定していきます。

Auth0でAPIを作成する

まずは、APIを作成します。APIsタブから Create API して、適当なAPIを作成します。この際、Identifierの項目には払い出されたAPI GatewayのURLを指定します。

JWTを取得する

APIが作成できたら、アクセストークンが取得できることを確認します。Testタブに移動しcurlのコマンドをコピーしてターミナルで実行します。

以下のようなJSONがレスポンスされれば成功です。後ほど使うので access_token をメモっておきます。

{
  "access_token": "xxxxx",
  "expires_in": 86400,
  "token_type": "Bearer"
}

API Gatewayに認証をサポートするように修正する

先程作成したAPI GatewayにJWTでクレームを検証できるように、OpenAPIのyamlを修正して、再度アップロードします。yamlを以下の用に修正します。 securityDefinitionssecurity を追加しています。 x-google-issuerx-google-jwks-uri にはAuth0のテナントのドメインを入力します。 x-google-audiences には、 ゲートウェイのエンドポイントを入力します。

swagger: '2.0'
info:
  title: sample-api
  description: Sample API on API Gateway with a Google Cloud Functions backend
  version: 1.0.0
securityDefinitions:
  auth0_jwk:
    authorizationUrl: ""
    flow: "implicit"
    type: "oauth2"
    x-google-issuer: "https://xxx.us.auth0.com/"
    x-google-jwks_uri: "https://xxx.us.auth0.com/.well-known/jwks.json"
    x-google-audiences: "https://xxx.an.gateway.dev/"
security:
  - auth0_jwk: []
schemes:
  - https
produces:
  - application/json
paths:
  /hello:
    get:
      summary: Greet a user
      operationId: hello
      x-google-backend:
        address: https://xxx.cloudfunctions.net/helloGET
      responses:
        '200':
          description: A successful response
          schema:
            type: string

yamlを修正したら、構成ファイルとして新たに追加します。

gcloud api-gateway api-configs create auth0-oauth2 --api=sample-api --openapi-spec=openapi.yaml

追加できたら、ゲートウェイの構成を追加した構成を使うように更新します。

gcloud api-gateway gateways update sample-gateway --api=sample-api --api-config=gogogle-oauth2 --location asia-northeast1

これで認証付きのAPI Gatewayを作成することができました。

ゲートウェイにリクエストしてみる

ヘッダにトークンを付与しないでリクエスト

認証付きのゲートウェイを作成できたので、リクエストして検証してみます。まずは、ヘッダにトークンを付与しない状態で /hello パスにアクセスしてみます。

Jwt is missing というエラーメッセージと401がレスポンスされました。トークンの検証処理が内部で行われていることが確認できます。

Bearerトークンを付与してリクエスト

クエリストリングに access_token を付与してリクエストしました。Hello World!と表示されました。JWTの検証が行われ CloudFunctions にリクエストが行ってることを確認できました。

まとめ

Google CloudのAPI Gatewayを触ってみました。OpenAPIのyamlを用意して特殊なアノテーションを記述するだけで、フルマネージドなWeb APIを作成することができました。OpenAPIのyamlをアップロードする以外に設定項目がほとんどなく、コンソールからできることはかなり限られていて、かなり割り切ったシンプルな印象を受けました。基本的には、複数のCloudFunctionsのエンドポイントをまとめるためにAPI Gateway + Cloud Functionsという構成で使うのが良さそうと思いました。

参考

https://cloud.google.com/api-gateway/docs/quickstart

https://cloud.google.com/api-gateway/docs/authenticating-users-auth0