第5章

Firebaseを開発用と本番用に分ける

Firebase接続、認証、データ設計、Security Rules、開発用・本番用プロジェクトの切り替えを学びます。

この章で学ぶこと

  • 開発用と本番用のFirebaseプロジェクトを分ける
  • FlutterFireでWeb、Android、iOSの接続設定を作る
  • Authentication、Firestore、Security Rulesを一緒に確認する

1. 2つのFirebaseプロジェクトを用意する

少なくとも次の2環境に分けます。

環境 例 用途
development idea-pocket-dev 開発、エミュレーター、テストデータ
production idea-pocket-prod 公開アプリ、本番利用者

本番データを手元の実験で削除しないこと、開発中の通知やテスト利用者を本番へ混ぜないことが目的です。請求やAPIの上限も環境ごとに確認します。

2. FlutterFireで接続する

Firebase CLIとFlutterFire CLIを準備し、各自の招待済みGoogleアカウントでログインします。

firebase login
firebase projects:list
dart pub global activate flutterfire_cli

プロジェクトのルートで対象プラットフォームを設定します。

flutterfire configure \
  --project=idea-pocket-dev \
  --platforms=web,android,ios \
  --out=lib/firebase_options_dev.dart

flutterfire configure \
  --project=idea-pocket-prod \
  --platforms=web,android,ios \
  --out=lib/firebase_options_prod.dart

PowerShellで複数行がうまく解釈されない場合は、コマンドを1行で入力してください。生成された設定にはアプリがFirebaseへ接続するための識別情報が含まれますが、Admin SDKの秘密鍵や署名鍵を入れてはいけません。

生成ファイルはどちらもDefaultFirebaseOptionsというクラス名になるため、importに別名を付けます。起動時にビルド定義から環境を選ぶ例です。

import 'firebase_options_dev.dart' as dev;
import 'firebase_options_prod.dart' as prod;

const appEnv = String.fromEnvironment('APP_ENV', defaultValue: 'dev');

final options = appEnv == 'prod'
    ? prod.DefaultFirebaseOptions.currentPlatform
    : dev.DefaultFirebaseOptions.currentPlatform;

await Firebase.initializeApp(options: options);
flutter run -d chrome --dart-define=APP_ENV=dev
flutter build web --release --dart-define=APP_ENV=prod

本番ビルドの既定値をdevにしておくと、指定忘れで誤った接続先になります。CIでは環境ごとにコマンドを固定し、ビルドログへProject IDを表示して確認します。

3. Authenticationを設定する

Firebase Consoleで各環境のAuthenticationを開き、必要なログイン方法だけを有効にします。Webでは承認済みドメイン、Googleログインでは同意画面、AppleログインではApple側のCapabilityや設定も必要になる場合があります。

最初の実習ではメール・パスワードまたは匿名認証のどちらか1つで十分です。利用者向けログイン方式と、開発チームがFirebase ConsoleへログインするGoogleアカウントは別物です。

ログイン状態を監視して画面を切り替えます。

StreamBuilder<User?>(
  stream: FirebaseAuth.instance.authStateChanges(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }
    return snapshot.data == null ? const SignInPage() : const IdeaListPage();
  },
)

4. FirestoreとSecurity Rulesを設定する

本人のカードだけを読み書きする最小ルール例です。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId}/ideas/{ideaId} {
      allow read, create, update, delete:
        if request.auth != null && request.auth.uid == userId;
    }
  }
}

これは出発点です。作成時の必須フィールド、文字数、変更できない所有者IDなども検証します。画面でボタンを隠すだけでは、不正なリクエストを防げません。

ローカルのEmulator Suiteで、次を自動テストします。

  • 未ログインでは拒否される。
  • 利用者AはAのカードを作成・取得できる。
  • 利用者AはBのカードを取得・変更できない。
  • 必須項目の欠落や長すぎる文字列を拒否する。

ルールを本番へ反映するときは、対象を明示します。

firebase deploy --only firestore:rules --project idea-pocket-prod

デプロイ前にfirebase login:listとProject IDを確認してください。

5. App Check、予算、ログを準備する

公開前に、次を検討します。

  • App Checkで正規アプリからのアクセスを検証する。
  • Google Cloudの予算アラートを設定する。
  • Authentication、Firestore、Functionsの利用量とエラーを確認する。
  • Cloud Functionsで生成AI APIを呼ぶ場合は、認証、レート制限、タイムアウト、費用上限を入れる。
  • 本番データを開発者が自由に閲覧しないようIAMを分ける。

予算アラートは利用を自動停止する仕組みとは限りません。急増時にどのサービスを止めるかも決めておきます。

やってみよう

  1. developmentとproductionのProject ID、アプリID、担当者を表にする。
  2. エミュレーターで、他人のカードを読めないテストを作る。
  3. developmentへ接続したWeb版で保存を確認する。
  4. 本番ビルドコマンドにAPP_ENV=prodが必ず含まれる仕組みを作る。