👋

Firebase でいきなり何百万も請求されないために

に公開

Firebase はお手軽にデータベースにアクセスすることが出来る便利なツールですが、一定量以上のアクセスがあると有料になるため、「アクセスポイントやキーがバレると、無限に課金される可能性がある」というリスクが存在します。

そうならないためにここでは、Firebase の主要な機能を追いかけながら、安全に、無限課金されないようにデータベースにアクセスする方法を考えてみます。
使用する機能は Auth、Firestore (or Realtime Db)、Functions、AppCheck です。

一番簡単なのは Spark プラン

無料で、自動的に有料に移行しないので、知らない間にお金を請求されることはありません。
ただし、アクセスが月に一定量を超えると停止するので、ある程度アクセスのあるアプリの場合は月の途中で使えなくなる……なんてこともあります。

ハイスコア登録とか、プレイヤーの選んだ選択肢の集計を取る……といったレベルであればこれで済みそうですが、反面チャットツールを作った場合はすぐ制限に達してしまうかも。

具体的な制限はこちらをご覧ください。

https://firebase.google.com/pricing?hl=ja

また、無料の Functions では HTTP リクエストで外部 API との連携が出来ないため、たとえば Functions 経由で OpenAI API にアクセスすることは出来ません。

なお、Spark(無料)とBlaze(有料)の切り替えはプロジェクトごとに可能です。
プロジェクトの左下に現在のプランが表示されていると思います。

Auth

Auth はどんなアプリでも実装しておいた方がいい最初のセキュリティと言えます。
これなしで「誰でも Firebase のデータを自由に読み書きできる」と、どう頑張っても偽アプリからのアクセスを防げない状態になるためです。

なお、登録されたユーザーのみのアクセス許可は Firestore で次のようなルールを適用します。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    // users コレクションの個別ドキュメントに対する制御
    match /users/{userId} {
      // 自分自身のドキュメントのみ読み書き可能
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }

    // それ以外のコレクションは読み書き禁止
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Auth のメール+パスワード登録ですが、実はメールといいつつ @ が入ってればなんでもいいので、匿名ユーザーとして使うこともできますし、SignInAnonymouslyAsync() という専用の匿名ユーザー生成関数も存在します。

AppCheck

  • Android、iOS の場合「正規のストアからインストールされたアプリか」を確認
  • Web の場合 reCAPTCHA で「正規のブラウザから来ているか?」を確認

この仕組みによって、偽アプリや curl から Firebase を叩くことが非常に困難になるため、適用できるのであれば必ず適用しておいた方がいい機能です。
一点難があるとすれば Windows アプリには全く機能しないことです。

❌アプリで直接 Firestore(データベース)を編集

非常に簡単にアクセスできるため、アプリから直接データベースの内容を書き換えたくなりますが、これはセキュリティリスクを高める悪手です。
AppCheck を行えば正規のアプリからのみ書き換え制限はできるかもしれませんが、実行中のアプリのメモリー改ざんによってデータが不正に書き換えることを防ぐ手段はありません。

なお、これは Realtime Db でも同様です。

⭕Functions を経由して Firestore を編集

リスクの低い方法なので、可能な限りこちらの方法を取るべきでしょう。

その際、出来れば onRequest ではなく onCall での実装を心がけましょう。
onRequest で実装してしまった場合、https://~?uid=XXXX で直接コールすることが可能になってしまうので、基本的には使うべきではありません。

どうしても使う場合はリクエストヘッダに Authorization: Bearer <ID_TOKEN> を付け、
サーバー側で admin.auth().verifyIdToken(token) を使って検証してください。

🎯まとめ

Auth(ユーザー登録)は必須、データベース更新は Functions 経由で行う、AppCheck が使えるなら使う。
ここまですると第三者からのアクセスを効果的に防ぎ、知らない間に無限課金地獄に落ちない設計が出来そうです。

Discussion