改めてFirebaseのAPI キーを理解する

この記事は「[ソニックガーデン プログラマ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden)」の13日目の記事です。

普段はRailsで開発しているのですが、Firebaseの知見も深めていきたいということで、今回はFirebaseのAPIキーについて改めて理解を深めたいと思います。

# そもそもAPIキーとは？
APIキーと聞くと、APIを利用するときに認証を行うための鍵というイメージがあります。
APIキーが漏れると、それを利用して悪意のある第三者がデータを取得するなどの操作ができるようになるため、通常外部に漏れないように環境変数に入れるなど慎重に管理する必要があります。

一方、FirebaseのAPIキーは、公式のページでも案内されている通り、公開されても問題がないとされています。

![スクリーンショット 2024-12-11 1.26.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169605/12f37ab0-a266-5d4e-8f1a-cba0afe1b555.png)

https://firebase.google.com/docs/projects/api-keys?hl=ja

そもそも、CSR（Client-Side Rendering）のアプリケーションを作る場合、コードを全てクライアント側に送信する必要があるため、APIキーも含めて理論上ユーザー側に秘匿することができません。（難読化して見つけにくくするなどはできますが）
そのため、FirebaseのAPIキーは通常のAPIキーとは違い、公開されても問題がないようにアプリの識別にのみ利用しており、認証の機能については別の機能で補う形の使い方を想定されています。

# 実際にAPIキーを利用してみる

Firebaseのプロジェクト作成の手順等は様々な記事があるので割愛しますが、環境構築さえできていれば、Firebaseへのアクセスは非常に短いコードで実現できます。

## Firestoreにデータを書き込んでみる
```javascript
import { initializeApp } from "firebase/app";
import { getFirestore, collection, addDoc } from "firebase/firestore";
 
const firebaseConfig = {
  apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
  projectId: "XXXXXXXXX",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

// Firestoreにレコードを追加する
await addDoc(collection(db, "users"), {
  first: "Ada",
  last: "Lovelace",
  born: 1815
});

```

## Firebase Authenticationでユーザーをサインアップしてみる
```javascript
import { initializeApp } from "firebase/app";
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";

const firebaseConfig = {
  apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
  projectId: "XXXXXXXXX",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
 
const email = 'test@example.com'
const password = 'password'
const auth = getAuth(app);
// ユーザー認証を追加する
await createUserWithEmailAndPassword(auth, email, password);
```

APIキーもプロジェクトIDもクライアントのコード内に含まれているため、これらを使えば上記のコードでどこからでもFirebaseにレコードを作成したり、ユーザーサインアップができたりすると思うとなかなか恐ろしいですね。
意図しない操作を行なわれないように、Firebase側で対策を行っておく必要があります。

# APIキーが公開されても問題ない状態にする

前述の公式ページを参考にしつつ、必要な対応を確認していきます。

## Firebase APIキーの許可リストに自動的に追加される API を確認する

Firebaseの画面からAPIキーを発行した時点で、利用できるAPIに自動で制限がかかる仕組みになっています。
Google Cloudコンソールを開いて、「APIとサービス」→「認証情報」を開くと、作成されたAPIキーを確認することができます。

![screenshot-console_cloud_google_com-2024_12_12-02_03_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169605/9a5c7fba-7281-f3a1-8d84-c3c0e6083f5b.png)

開いてみると、選択されているAPIのリストも確認できます。

![スクリーンショット 2024-12-12 2.05.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169605/8a185418-585b-277b-8698-90d504aabb45.png)

以下のページにFirebaseの各サービスを利用するのに必要なAPIが記載されているので、利用していないサービスに関するAPIについてはリストから削除しておくと、不要な権限を減らすことができます。

https://firebase.google.com/docs/projects/api-keys?hl=ja#faq-required-apis-for-restricted-firebase-api-key

## APIキーを使用できるサイトを制限する

APIキーの許可リストを確認した同じページで、APIキーを使用できるサイトを制限することができます。
ウェブからしか利用しないのであれば、以下のように「ウェブサイト」を選択した上で、許可するウェブサイトのURLを追加します。

![スクリーンショット 2024-12-12 18.25.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169605/a6d45a5b-cb24-4337-55ea-306f61870ac2.png)

上記のように制限をかけると、試しにlocalhostからリクエストしたところ正しくブロックされました。

![スクリーンショット 2024-12-12 18.36.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169605/76a2bc37-4d41-4cfb-4134-84a43424dcab.png)


## Firebase Security Rulesを適切に設定する

前述の通り、APIキーを使うとFirestoreに好き勝手にデータを書き込んだり読み取ったりができてしまうため、必要な処理のみできるようにFirebase Security Rulesを適切に設定する必要があります。

Firestoreを有効化すると、以下のように「本番環境モード」か「テストモード」どちらかを選ぶ選択肢が表示されます。
![スクリーンショット 2024-12-10 17.45.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169605/12e95155-9aaa-f38e-96ac-ebbb4f327e67.png)

「テストモード」を選んだ場合、作成後1ヶ月間は読み書き権限が制限なし（1ヶ月後まではtrueを返している）になるので、期限までは自由に読み書きができる状態になります。
期限が1ヶ月に設定してあるのは、プロジェクト作るだけ作って忘れてしまっていたとしても、不正にアクセスされてしまうリスクを下げるための親切設計なんだと思います。
ちなみに本番環境モードを選択すると、初期設定は`allow read, write: if false;`になっているので、適切なSecurity Rulesを設定しないと一切データにアクセスできない状態になります。

Security Rulesを利用することで、読み書きのホワイトリストを作っていくイメージで必要なアクセスのみ許可していくことができます。
例えば、以下のように設定していれば、ログインしており、かつ自分自身のユーザーのデータのみ参照できるが、書き込みなどはできない、といった設定ができます。
APIキーが漏れたとしても、必要なデータしか読み書きされない状態になると、だいぶ安心感が増しますね。
```
rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    function isSignedIn() {
      return request.auth != null;
    }
    function isOwn(userId) {
      return isSignedIn() && request.auth.uid == uid;
    }

    match /users/{userId} {
      allow get: if isOwn(userId);
    }
  }
}

```

## Firebase 以外のサービスには、制限された個別のAPIキーを使用する

前述のAPIキーの許可リストから、Google Cloudに関する各APIの権限を追加することも理屈上可能ですが、FirebaseのAPIキーは公開することを前提としているため、その場合は個別のAPIキーを別途用意することが推奨されています。

## パスワード ベースの Authentication を使用する場合の割り当て量の確保

Google Cloudコンソールを開いて、「APIとサービス」→「有効なAPIとサービス」→「Identity Toolkit API」→「割り当てとシステム上限」を開くとユーザー認証まわりのエンドポイントの割り当てを設定できます。（1分間あたりサインインを何回までできるなどの制限を調整できる）
ブルートフォース攻撃の可能性を回避するためには、ここの制限を抑制することが推奨されていますが、どのぐらいの値が妥当なのかは運用しながらでないと難しそうですね。

https://console.cloud.google.com/apis/api/identitytoolkit.googleapis.com/quotas?project=_&hl=ja

## 環境固有のAPIキーを使用する

これも前述の不要な権限を持たせない話と近いですね。
本番環境とテスト環境など複数の環境を作成する時には、APIキーを共用するのではなく個別に発行することで、それぞれのリスクを下げる、という話だと思います。
ただ、APIキーだけ個別に発行したとしても、Firebaseプロジェクトを共用していると、テスト環境のAPIキーで本番環境のデータにアクセスできるといったリスクが発生します。
そのため、複数環境を作成する時は、はじめからFirebaseプロジェクト自体を分けたほうが安心です。

## Firebase App Checkを使う

不正アクセスを防ぐための仕組みとして、Firebase App Checkというサービスが提供されています。
まだ使ったことがないので詳しい動作はわかりませんが、正規のアプリから送信されたリクエストなのかを検証してくれたり、reCAPTCHA v3の仕組みで不正アクセスを判定してくれたりする仕組みのようです。

# まとめ
FirebaseのAPIキーは通常のAPIキーとは違い、公開されてしまっても問題ないですが、その分Security RulesやAPIの制限等、他の部分で気をつける必要がある部分があることがよくわかりました。
通常のクライアント・サーバー構成のシステムとは勘所もだいぶ変わってくるので、今後も手を動かしつつ理解を深めていきたいと思います。

明日は「[ソニックガーデン プログラマ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden)」の14日目、 @pi_chan の記事になります。
お楽しみに。