つい最近, 学生に見られてしまった42歳*1(外資コンサル企業マネージャー)です.
今, プライベート(いわゆる個人開発)でWebサービスを開発しようとしているのですが,
- Webサービスの性質上, 何らかの認証機能が必須
- ↑が必須なのはわかるけど, 自分で開発(コードを書く)のは正直つらい(かつ自前で書くほどの要件でも無さそう)
- クラウド(今回はGoogle Cloud)で運用するのは決まってるので, 既存のクラウドサービスでどうにかならないか?
といういい感じにサボる効率的なソリューションを探した結果,
ワイ「どうやらCloud EndpointsとFirebaseでどうにかなるらしい🤔」
とわかり, 実際にどうにかなりそうなものができたので, 自分が忘れないように(&似たような状況の方のお役にたてるように)メモとして残したいと思います.
TL;DR
Google Cloudでサクッと認証をやりたいときは「Identity-Aware Proxy (IAP) 」「Cloud Endpoints」「Firebase Authentication」を使うとひとまずいい感じにできます
少なくとも, メールとかの一般的な認証は.*2
おしながき
やったこと
今回やったこと(やりたかったこと)を言語化すると,
- BackendのAPI(RESTful API)はApp Engine(Standard)にある. これはすでに動いている.
- 認証の方式は, 「メールアドレスとパスワード」という非常にシンプルな方式.
- 何かしらの認証をして, Token(有効期限付き)を取得&APIコールはそれを使い回す.
- APIは認証する所としないところがある.
- ルート
/
については認証不要とする. 外形監視のHealth Checkのパスとして使うため(認証でガードしてしまっては困ってしまう). - 上記以外のパスはすべて認証済みのTokenを必須とする.
- ルート
この条件のもと, いろいろ考えて試行錯誤と実験をした結果, 以下のような構成(概念です, 後で触れますが実際の構成は少し変わります)でまとまりました.
- User(実態としてはフロントエンドのアプリケーションを想定)はFirebase Authenticationを使って認証を行い, Tokenを取得する
- Userは, 1.で得たトークンをAPI Requestに埋め込んで必要なAPIリソースにアクセスする
- API ProxyはTokenをチェック(Firebase Authenticationとの突合)を行い, 通して良いアクセスかどうかをチェックする.
- ちゃんとしたTokenならApp Engineに通す, NGなら401 Errorで弾く
若干のハマりどころはありましたが, この構成でどうにか動きました.
実際の構成
この先は実際どうやって作ったか?という話です.
実際のアーキテクチャはこんな感じになります.
登場するサービスは,
- 認証基盤としてのFirebase Authentication. これは先程の絵と同じ.
- API Proxy(もしくはAPI Gateway)としてのExtensible Service Proxy V2(ESPv2). Cloud Endpointsを使って構成する. ホストするのはCloud Run.
- 先程の概念図では分けて書いてました(理解を整理するため).
- 実際動くものとしては, 「Cloud EndpointsのESPv2イメージを元にコンテナとして動かします, 環境はCloud Runでね」って感じ.
- ホスト環境はCloud Run, 上モノはCloud Endpointsという雑な理解で大丈夫です.
- API本体はApp Engineでホスト. これも先程の絵と同じ.
で, 実際の作業手順としては,
- App Engineのへのアクセスを「Identity-Aware Proxy (IAP) 」で保護. 上の図で言う所の「4. 優勝」とある線の部分のアクセスを制御.
- 認証用のESPv2を構築し, App Engineまでのパスを通す(この時点ではAPIアクセスに認証を入れない)
- Firebase Authenticationでユーザー作成, ESPv2の認証基盤として紐付けして, APIアクセス認証するようにする
という感じになります.
IAPでApp Engineのアクセスを保護
普通にデプロイしたApp EngineのアプリケーションはURLを知ってる人なら全世界に見える状態です.
このままだと外から認証を掛ける意味がないので, Identity-Aware Proxy (IAP) でアクセスを保護します.
これがちゃんと終わると, 許可したユーザーのみのアクセスとなります(ブラウザのシークレットモードなどで確認すると良いでしょう).
なお, 個人的なオススメとしては, 自分のGmailアカウントや組織などを予め表示許可を与える
事だったりします.
デバッグしたり, なにかの確認作業をしたりという所で「自分だけ見たい」って事はあると思うので.
認証用のESPv2をCloud Runにデプロイ
App EngineのIAP保護が終わったら, API Proxyの役割を果たすESPv2を構築してCloud Runにデプロイします.
これは, Google Cloudにまとまってる手順をそのままやるだけです.
ちょっとややこしい手順ですが, 基本そのまま真似をするといけます.
この時点でのわたしがやったopenapi-appengine.yaml
の中身はこんな感じになりました.
swagger: "2.0" info: title: Cloud Endpoints + App Engine description: Sample API on Cloud Endpoints with an App Engine backend version: 1.0.0 host: ${Cloud RunのURL} schemes: - https produces: - application/json x-google-backend: address: ${App EngineのURL} jwt_audience: ${App EngineのIAPクライアントID} protocol: h2 paths: /: get: summary: Health check operationId: index responses: "200": description: A successful response schema: type: string /predict: post: summary: predict a home run operationId: predict responses: "200": description: A successful response schema: type: string
ポイントとしては,
- yamlの記述内容は
App Engineにあるアプリの定義
なのか,API Proxyの定義そのもの
なのかを分けて考えると理解・記述がしやすい - 公式の説明では端折ってますが, 「使いたいAPIのパス」はこの時点で
paths
に定義が必要- 公式のやつをそのままやると,
/
しかpath通らない - なぜなら, 「Proxyの設定」なので, ここで使うべきパスを許可する必要があるため
- 公式のやつをそのままやると,
招待はOpen API(swagger)なので, 予め定義を用意すると楽かもしれないです.*3
ここまでうまくいくと,
- API ProxyのURL(Cloud RunのURL)経由でApp Engineのアプリにアクセスできる
- どのパスも, 認証いらずで通過する
- App EngineのURLはIAPで許可した者のみ通す
といった状態になります.
Firebase Authenticationを設定
この手順は以下のサイトが参考になりました.
ここまで行けたら優勝まであと一歩です.
Firebase Authenticationを有効化してユーザーを作ります(これはGUIでポチポチしたら終わる).
認証とアクセスはフロントエンドのアプリからやりたいところですが...ひとまずAPIの検証がしたい!感じであればcurlコマンドでどうにかします.
curl 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${自分のAPI Key}' \ -H 'Content-Type: application/json' \ --data-binary '{"email":"${your email address}","password":"${your password}","returnSecureToken":true}'
これでレスポンスに含まれるidToken
を控えておきます.
先ほどCloud RunにデプロイしたAPI Proxyに「Firebase Authenticationを認証基盤にしていい感じにしてくれ」という設定をします.
swagger: "2.0" info: title: Cloud Endpoints + App Engine description: Sample API on Cloud Endpoints with an App Engine backend version: 1.0.0 host: ${Cloud RunのURL} schemes: - https produces: - application/json x-google-backend: address: ${App EngineのURL} jwt_audience: ${App EngineのIAPクライアントID} protocol: h2 securityDefinitions: firebase: authorizationUrl: "" flow: "implicit" type: "oauth2" x-google-issuer: https://securetoken.google.com/${自分のプロジェクトのID} x-google-jwks_uri: https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com x-google-audiences: ${自分のプロジェクトのID} paths: /: get: summary: Health check operationId: index responses: "200": description: A successful response schema: type: string /predict: post: summary: predict a home run operationId: predict security: # 認証が必要やでっていう設定 - firebase: [] responses: "200": description: A successful response schema: type: string
最初にデプロイしたyamlとの差分は,
securityDefinitions
で「認証基盤はFirebase Authenticationだよ」と定義/
以外のパス, ここでは/predict
というパスに「認証が必要だよ」と定義
です, ここまでできたらCloud Runに再デプロイします.
こんな感じで動作したら成功です.
$ # indexはTokenいらない
$ curl --location --request GET 'https://example.a.run.app'
{"status":"ok"}
$
$ # predictでToken無しだと怒られる
$ curl --location --request POST 'https://example.a.run.app/predict' \
--header 'Content-Type: application/json' \
--data-raw '{
"throw": "R",
"pitch_speed_mph": 100,
"pitch_type": "FF"
}'
{"message":"Jwt is missing","code":401}
$ # Tokenをつけると動くよ
$ curl --location --request POST 'https://example.a.run.app/predict' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ${控えておいたidToken}' \
--data-raw '{
"throw": "R",
"pitch_speed_mph": 100,
"pitch_type": "FF"
}'
{"result":"HOME_RUN"}
これでOAuth2ベースのトークン認証がほぼ開発ゼロでできるようになりました, おめでとうございます🙌🏻
こんなときどうする🤔
というわけで, 「App Engine上のAPI認証をCloud Endpoints + Firebaseでいい感じにできた」訳ですが, 「こんな時どうする」編を少々.
ホストをApp Engineじゃないサービスに
結論から言っちゃうと, IAPで保護をかけられるサービスならなんでもOKです.
現実的な線で言うと, 今回紹介したApp Engine以外だと,
- Cloud Run
- GKE
- GCE
あたりがよく使うんじゃないかなと思います.
ESPv2を立てる時のx-google-backend
の設定をいじるとだいたい行けると思います.
「認証」じゃなくて「認可」をやりたい
これは自分の認識として,
あくまでL7(アプリケーションのレイヤー)での認証を実現するものであり, 「特定の機能を特定のユーザーに許可する」ような認可はできない
かなと思っています.
IAPみたいにIAMユーザーで絞る感じならある程度コントロールできるかと思います*4が, API Proxyはあくまでも「土管」なので, 特定のユーザーに対する処理の許可はアプリケーション上で自分で作れや!が正解かなと.
もし認識違ってたらツッコミください.
結び
というわけで, 「App Engine上のAPI認証をCloud Endpoints + Firebaseでいい感じにしてみた」という話を紹介しました.
このエントリーは自分の作業メモ・考え整理として書きましたが, おそらく仕事でも役たちそうだなと思いました.
認証周りは自分で作ると結構きっつい(個人的には苦手意識もめっちゃある)ので, こういう機構は積極的に使おうと思います.
最近ちゃんと触れてないですがこれってきっとAWSでも似たような事できますよね?もしご存知な方がいましたら紹介してもらえると嬉しいです.
最後までお読み頂きありがとうございました.