ASP.NET Core 6.0 を Cloud Run で動かして FirebaseAdmin で Token を取得できるAPIを組み込む手順

# はじめに

ASP.NET Core 6.0 を Cloud Run で動かして FirebaseAdmin で Token を取得する手順を示します。
次の理由から書くことにしました。
* 必要があって調べたところ、ASP.NET Core 5.0 の記事しか見つからなかった。
* [.NET 6では Dockerfile を使わなくても Deploy 出来るようになった](https://cloud.google.com/run/docs/deploying-source-code?hl=ja)ことも併せて伝えたい。

## 謝辞

以下の記事を参考にさせていただきました。
* [Using the Firebase Admin SDK for .NET in Google Cloud Run - medium.com](https://medium.com/firebase-developers/using-the-firebase-admin-sdk-for-net-in-google-cloud-run-e44b12758bf7)

# 前提スキル／環境

* Visual Studio 2022 と `ASP.NET and web development` ワークロード のセットアップを完了している。
* 公式トレーニング 「[ASP.NET Core コントローラーを使用して Web API を作成する](https://learn.microsoft.com/ja-jp/training/modules/build-web-api-aspnet-core/)」 の `ASP.NET Core Web API コントローラー` まで進められること。
* Google Cloud に 対象とするプロジェクトが作成されていること。
* プロジェクトのGoogle Cloud Run のサービスを作成できるアカウントを持っていること。
* 作業端末に [gcloud CLI をインストール](https://cloud.google.com/sdk/docs/install?hl=ja)してあること。

# 検証環境

(Windows)
* Visual Studio 2022 17.4
* dotnet sdk 6.0.403

# 本編

## 目次

1. ASP.NET Core Web API プロジェクトを作成する。
1. Cloud Run の要件に合わせて修正し、Cloud Run に deploy する。
1. Google Cloud で 「Cloud Run のサービスで Token を作成する権限」をサービスアカウントに付与する。
1. プロジェクトに FirebaseAdmin を組み込み、動作確認を行う。
1. もしうまくいかない時は
1. (おまけ) Google Cloud で Firestore に接続する方法

## (1) ASP.NET Core Web API プロジェクトを作成する。

Visual Studio 2022 で、テンプレート「ASP.NET Core Web API」を選択してプロジェクトを新規作成します。

※公式ドキュメントの場所も貼っておきます: [チュートリアル: ASP.NET Core で Web API を作成する](https://learn.microsoft.com/ja-jp/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0&tabs=visual-studio)

※dotnet のバージョンを指定する場合は、プロジェクトディレクトリに次のファイルを追加します。vetsionの部分は、[dotnet sdk](https://dotnet.microsoft.com/en-us/download) のバージョンを指定します。

```json:global.json
{ "sdk": { "version": "6.0.403" } } 
```

※インストールされている dotnet sdk のバージョンの確認するには、Developer PowerShell で次のコマンドを実行します。
```powershell
dotnet --list-sdks
```

※現在適用されている dotnet sdk のバージョンの確認するには、Developer PowerShell で次のコマンドを実行します。
```powershell
dotnet --version
```

## (2) Cloud Run の要件に合わせて修正し、Cloud Run に deploy する。

要件は、2つあります。
1. PORTを環境変数から取得すること
1. ルートパスで 200(OK)応答を返すこと

この要件を満たさないと、Cloud Run に deploy する際の ビルドプロセスにおける validation が失敗してしまいます。 

1つ目は、Program.csを次のように修正します。
```c#:Program.cs
//app.Run();
var port = Environment.GetEnvironmentVariable("PORT");
if (port == null)
{
    // デバッグ実行用
    app.Run();
}
else
{
    // Cloud Run実行用
    app.Run($"http://0.0.0.0:{port}");}
}
```

2つ目は、Program.csを次のように修正します。
公式ガイドの通り、`Hello World!` を応答するようにしていますが、TARGET環境変数を使う部分も含めて特に意味はなく、200応答を返していれば良い？ （ここは公式ドキュメントを見つけられていない・・・ご存じの方、ご教授ください）
```c#:Program.cs
    // Cloud Run実行用
    app.MapGet("/", () => $"Hello {Environment.GetEnvironmentVariable("TARGET") ?? "World"}!"); //<-追加
    app.Run($"http://0.0.0.0:{port}");}
```
以上を保存したら、 Cloud Run に deploy します。
Developer PowerShell で次のコマンドを実行します。
```powershell
gcloud config set project {対象プロジェクトのID}
gcloud run deploy
```
* `Service name`を聞かれますので、デフォルトから変更したい場合は入力します。
* `reasion`を聞かれますので、列挙された選択肢の中から番号を入力します。
* Cloud Runにまだ登録されていないサービス名を指定した場合、`Allow unauthenticated invocations(未認証リクエストを許可するかどうか)`を聞かれますので、回答します。

その結果、次のように出力されれば、成功です。
```
Building using Buildpacks and deploying container to Cloud Run service [{Cloud Run サービス名}] in project [{対象プロジェクトのID}] region [us-central1]
✓ Building and deploying new service... Done.
  ✓ Uploading sources...
  ✓ Building Container... Logs are available at [{Cloud Build の URL}].
  ✓ Creating Revision... Revision deployment finished. Checking container health.
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [{Cloud Run サービス名}] revision [{Cloud Run のリビジョン名}] has been deployed and is serving 100 percent of traffic.
Service URL: {Cloud Run サービスの公開URL}
```

参考: Googleの公式ガイド「[Cloud Run に .NET サービスをデプロイする - サンプル アプリケーションを作成する](https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-dotnet-service?hl=ja#writing)」

## (3) Google Cloud で 「Cloud Run のサービスで Token を作成する権限」をサービスアカウントに付与する。

[Google Cloud で IAMと管理](https://console.cloud.google.com/iam-admin/iam)にアクセスします。
※もし `IAM API` が有効になっていない場合は、[Identity and Access Management (IAM) API](https://console.developers.google.com/apis/api/iam.googleapis.com/overview) にアクセスして有効にしましょう。

対象プロジェクトの IAM のページにある、「プリンシパル別に表示」された権限を見てください。
その中に、名前が `Default compute service account` となっている行があるはずです。
その行の編集アイコンをクリックし、ロール `サービス アカウント トークン作成者` を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/ef91f180-5807-d610-6ebc-23e38e0ce904.png)

これで「Cloud Run のサービスで Token を作成する権限」が付与されたことになりました。

## (4) プロジェクトに FirebaseAdmin を組み込み、Cloud Run に deploy して動作確認を行う。

まず、ASP.NETプロジェクトに、`FirebaseAdmin` パッケージを追加します。(執筆時点のバージョンは 2.3.0)

次に、Program.cs に、FirebaseAdmin を初期化するコードを追加します。
```c#:Program.cs
    // Cloud Run実行用
    FirebaseApp.Create(); //<-追加
    app.MapGet("/", () => $"Hello {Environment.GetEnvironmentVariable("TARGET") ?? "World"}!");
    app.Run($"http://0.0.0.0:{port}");}
```

次に、Controllersに、Tokenを取得するAPIを追加します。この記事では Custom Tokenを例に挙げています。
```c#:Controllers/GoogleCustomAuthController
using Microsoft.AspNetCore.Mvc;

namespace Example.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class GoogleCustomAuthController : ControllerBase
    {
        private readonly ILogger<GoogleAuthController> _logger;

        public GoogleCustomAuthController(ILogger<GoogleAuthController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        [Produces("text/plain")]
        public Task<string> Get([FromQuery] string uid)
        {
            try
            {
                return FirebaseAdmin.Auth.FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "CreateCustomToken failed.");
                throw;
            }
        }
    }
}
```

以上を保存したら、 Cloud Run に deploy します。
Developer PowerShell で次のコマンドを実行します。
```powershell
gcloud run deploy
```

成功したら、Cloud Run サービスの公開URL にアクセスし、Tokenを取得するAPIを試してみましょう。
APIのパスは、`{Cloud Run サービスの公開URL}/GoogleCustomAuth?uid=abcde` という感じです。 

こんな感じの結果が返ってきたら成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109362/04390096-f0d7-5134-3c64-61f017cf759f.png)

## (5) もしうまくいかない時は

* ソースコードが Cloud Run に反映されていない？と思うとき
  * Visual Studio で「すべて保存」を実行してからリトライする
  * Cloud Run のサービスを消してから（または新しいサービス名を指定して） deployする ※私はこれが必要になったことがあります

## (おまけ) Google Cloud で Firestore に接続する方法

`FirebaseApp.Create()` の呼出しが完了しているならば、次のコードだけで Firestore DB インスタンスを生成することができます。

```c#
using Google.Apis.Auth.OAuth2;
using Google.Cloud.Firestore;

FirestoreDb firestoreDb = new FirestoreDbBuilder() { GoogleCredential = GoogleCredential.GetApplicationDefault() }.Build();

```

# おわりに

基本的に情報をまとめ直しただけの記事になりましたが、ぜひストックして活用いただけると嬉しいです。