この記事について
Webアプリの認証基盤をFirebaseにオフロードできるFirebase AuthをGCP API Gatewayで試してみる。APIの実行を認証済みユーザーに制限したいケースに適用できるパターン。
環境準備が長くなるので3回に記事を分ける。
- 準備編
- Firebase Auth(メール/パスワード)編
- Firebase Auth(Google認証)編
構成図
準備編ではWeb APIをホストするCloud Run(要認証)をデプロイし、API Gatewayのバックエンドとして構成する。
サンプルWeb API
Cloud RunにデプロイするAPI Flaskを使った各言語の挨拶を返す簡単なサンプルアプリのソースは以下の通り。なお、API Flaskは軽量Web APIフレームワークである。
/greetings/{id:int}に数字を与えると、idに応じた挨拶が返ってくる。
from apiflask import APIFlask, Schema, abort
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf
app = APIFlask(__name__)
greetings = [
{'id': 0, 'name': 'en', 'category': 'Hi'},
{'id': 1, 'name': 'ja', 'category': 'Konichiwa'},
{'id': 2, 'name': 'es', 'category': 'Hola'}
]
class GreetingIn(Schema):
name = String(required=True, validate=Length(0, 2))
category = String(required=True, validate=OneOf(['en', 'ja','es']))
class GreetingOut(Schema):
id = Integer()
name = String()
category = String()
@app.get('/greetings/<int:greeting_id>')
@app.output(GreetingOut)
def get_greeting(greeting_id):
if greeting_id > len(greetings) - 1:
abort(404)
return greetings[greeting_id]
@app.patch('/greetings/<int:greeting_id>')
@app.input(GreetingIn(partial=True))
@app.output(GreetingOut)
def update_greeting(greeting_id, data):
if greeting_id > len(greetings) - 1:
abort(404)
for attr, value in data.items():
greetings[greeting_id][attr] = value
return greetings[greeting_id]
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=False)
from apiflask import APIFlask, Schema, abort
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf
app = APIFlask(__name__)
greetings = [
{'id': 0, 'name': 'en', 'category': 'Hi'},
{'id': 1, 'name': 'ja', 'category': 'Konichiwa'},
{'id': 2, 'name': 'es', 'category': 'Hola'}
]
class GreetingIn(Schema):
name = String(required=True, validate=Length(0, 2))
category = String(required=True, validate=OneOf(['en', 'ja','es']))
class GreetingOut(Schema):
id = Integer()
name = String()
category = String()
@app.get('/greetings/<int:greeting_id>')
@app.output(GreetingOut)
def get_greeting(greeting_id):
if greeting_id > len(greetings) - 1:
abort(404)
return greetings[greeting_id]
@app.patch('/greetings/<int:greeting_id>')
@app.input(GreetingIn(partial=True))
@app.output(GreetingOut)
def update_greeting(greeting_id, data):
if greeting_id > len(greetings) - 1:
abort(404)
for attr, value in data.items():
greetings[greeting_id][attr] = value
return greetings[greeting_id]
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=False)
Dockerイメージのビルド
リポジトリ作成
$ gcloud artifacts repositories create docker-repos \
--repository-format=docker \
--location=asia-northeast1\
--description="greetings"
実行結果
# curl https://${apigw-identifier}.an.gateway.dev/greetings/0
{"category":"Hi","id":0,"name":"en"}
Dockerイメージのビルド
Dockerfile
FROM python
COPY . /app
WORKDIR /app
RUN pip install --upgrade pip
RUN pip3 install flask
RUN pip3 install apiflask
EXPOSE 8080
ENTRYPOINT [ "python3" ]
CMD [ "app.py" ]
ビルド
$ gcloud builds submit --region=global --tag asia-northeast1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/docker-repos/greetings
Cloud Runのデプロイ
デフォルトでは認証が必要なCloud Runがデプロイされる。
resource "google_cloud_run_service" "greetings" {
project = local.project_id
name = "greetings"
location = "asia-northeast1"
template {
spec {
containers {
image = "asia-northeast1-docker.pkg.dev/${local.project_id}/docker-repos/greetings:latest"
}
}
}
traffic {
percent = 100
latest_revision = true
}
}
API Gatewayのデプロイ
API
resource "google_api_gateway_api" "greetings" {
provider = google-beta
api_id = "greetings"
}
API Config
gateway_config内のbackend_configでCloud RunのInvoker(実行)権限を持たせるサービスアカウントを指定している。
これによりGCP IAMで保護されたCloud RunをAPI Gatewayのバックエンドとして構成できる。
resource "google_api_gateway_api_config" "greetings_cfg" {
provider = google-beta
api = google_api_gateway_api.greetings.api_id
api_config_id = "greetings-config"
openapi_documents {
document {
path = "openapi2-run.yaml"
contents = filebase64("openapi2-run.yaml")
}
}
lifecycle {
create_before_destroy = true
}
gateway_config{
backend_config{
google_service_account="apigw-sa@${local.project_id}.iam.gserviceaccount.com"
}
}
depends_on = [
google_api_gateway_api.greetings,google_project_iam_member.project
]
}
API Gateway
resource "google_api_gateway_gateway" "api_gw" {
provider = google-beta
api_config = google_api_gateway_api_config.greetings_cfg.id
gateway_id = "greetings-gateway"
depends_on = [
google_api_gateway_api_config.greetings_cfg
]
}
あとがき
後半のFirebase Authentication編に続く。