Firebase面接質問2026:Flutterの認証、Firestore、セキュリティルール

Flutter開発者向けのFirebase面接質問とDartによる回答:認証状態ストリーム、Google Sign-In v7、FirestoreのCRUDとモデリング、セキュリティルール、カスタムクレーム、トランザクション、FlutterFire 5の変更点。

Firebase面接質問2026:Flutterの認証、Firestore、セキュリティルール

Flutter職種で問われるFirebaseの面接質問は、5つのテーマに集中します。3種類の認証状態ストリーム、Firestoreのデータモデリング、セキュリティルール、オフライン時の書き込み、そしてすべての読み取りに付随するコストモデルです。2026年もFirebaseはFlutterアプリの標準的なバックエンドであり、2026年10月以降、FlutterFireプラグインはBoM 5.x(firebase_core 5.1.0、firebase_auth 7.0.1、cloud_firestore 7.1.0)として提供されています。AndroidのminSdkは24に引き上げられ、内部ではFirebase Apple SDK 13が使われています。

FlutterFire BoM 5.1.0(2026年10月)

現在のFlutterFireリリースはBoM 5.1.0で、firebase_auth 7.0.1、cloud_firestore 7.1.0、firebase_core 5.1.0を含みます。メジャーバージョン5.0.0ではAndroidのminSdk 24が必須となり、Firebase Apple SDK 13.0.0がベースになり、App Checkのactivateのパラメータ名が変更されました。cloud_firestore 6.3.0で追加されたFirestore Pipelinesは、リリースごとに使える式が増えています。

2026年のFirebase面接で問われること

面接官がAPIの細かい暗記を問うことはほとんどありません。確認されるのは、Firebaseがどこでルールを強制するのか、各操作にどれだけのコストがかかるのか、ネットワークが切れたときに何が起きるのかを候補者が理解しているかどうかです。次の表は、繰り返し出題されるテーマと、評価される回答に含まれるべき要素をまとめたものです。各テーマは後半のFlutter開発者向けFirebase面接質問で詳しく回答しています。

テーマ典型的な質問評価される回答の要素
認証ストリームauthStateChanges()、idTokenChanges()、userChanges()の違いそれぞれが発行するイベントと、画面遷移のガードに使うべきもの
セキュリティアクセス制御はどこで強制されるかサーバー側のセキュリティルールであり、クライアント側のチェックではない。App Checkは別の層
ロール管理者ロールをどう実装するかAdmin SDKで設定するカスタムクレームをrequest.auth.tokenで読み取る
データモデリングチャットアプリをモデリングするサブコレクション、最新メッセージの非正規化、1メッセージ1ドキュメント
整合性トランザクションかバッチ書き込みかトランザクションは読み取ってから書き込み、リトライされる。バッチは書き込みのみをアトミックに行う
オフラインオフライン時の書き込みはどうなるかローカルキャッシュ、hasPendingWrites、再接続時の同期
コストなぜこの画面は高くつくのかドキュメント読み取り単位の課金、リスナー、カーソルによるページネーション
比較FirestoreかRealtime Databaseかクエリモデル、スケーリング、課金単位

Firebaseプロジェクトのセットアップ

FlutterアプリケーションでFirebaseを使用するためには、まずFirebase CLIとFlutterFire CLIを使用してプロジェクトを初期化する必要があります。flutterfire configureコマンドを実行すると、プラットフォーム固有の設定ファイルが自動生成されます。このプロセスにより、iOS、Android、Web、macOS、Windows向けの設定が一括で管理できるようになります。

設定が完了したら、アプリケーションのエントリーポイントでFirebaseを初期化します。この初期化処理は非同期で行われるため、WidgetsFlutterBinding.ensureInitialized()を事前に呼び出す必要があります。

main.dartdart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Initialize Firebase with platform-specific config
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

DefaultFirebaseOptions.currentPlatformは、FlutterFire CLIによって自動生成されるfirebase_options.dartファイルから読み込まれます。この仕組みにより、開発者はプラットフォームごとの設定を意識することなく、統一されたコードベースでマルチプラットフォーム対応を実現できます。

Firebase Authentication:メール認証とソーシャルログイン

Firebase Authenticationは、メール・パスワード認証、OAuthプロバイダ(Google、Apple、GitHub)、電話番号によるSMS認証、多要素認証(MFA)をサポートしています。TOTPによる多要素認証は、firebase_auth 6.1.0以降macOSでも利用できます。

以下は、メール・パスワード認証を実装するサービスクラスの例です。エラーハンドリングでは、FirebaseAuthExceptionのエラーコードに基づいて適切なメッセージを返すことで、ユーザーに分かりやすいフィードバックを提供します。

auth_service.dartdart
import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Register with email and password
  Future<User?> register(String email, String password) async {
    try {
      final credential = await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      return credential.user;
    } on FirebaseAuthException catch (e) {
      // Handle specific error codes
      switch (e.code) {
        case 'email-already-in-use':
          throw Exception('This email is already registered');
        case 'weak-password':
          throw Exception('Password must be at least 6 characters');
        default:
          throw Exception('Registration failed: ${e.message}');
      }
    }
  }

  // Sign in with existing credentials
  Future<User?> signIn(String email, String password) async {
    final credential = await _auth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
    return credential.user;
  }

  // Reactive auth state stream
  Stream<User?> get authStateChanges => _auth.authStateChanges();
}

authStateChangesストリームを使用することで、認証状態の変化をリアクティブに監視できます。これにより、ログイン・ログアウト時のUI更新を自動的に行うことが可能です。StreamBuilderやRiverpodなどの状態管理ライブラリと組み合わせることで、宣言的なUIを構築できます。

パスワードのルールをクライアント側だけに置く必要はもうありません。validatePassword API(firebase_auth 6.0.0以降)は、Firebaseコンソールで設定したポリシーに照らしてパスワードを検証します。ポリシーでは最小文字数、大文字、小文字、数字、特殊文字を必須にできます。

Googleサインインの実装

OAuthプロバイダには追加のパッケージが必要です。google_sign_in 7.0以降、プラグインはGoogleSignIn.instanceで取得するシングルトンになり、初期化は一度だけ行う必要があり、認証(authentication)と認可(authorization)が分離されました。古いチュートリアルにあるGoogleSignIn().signIn()の呼び出しは、もうコンパイルが通りません。

google_auth.dartdart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

// Call once at startup, before any other GoogleSignIn method
Future<void> initGoogleSignIn() => GoogleSignIn.instance.initialize();

Future<UserCredential> signInWithGoogle() async {
  try {
    // Shows the native account picker; throws if the user cancels
    final googleUser = await GoogleSignIn.instance.authenticate();

    // Synchronous getter since v7: only the ID token is exposed here
    final googleAuth = googleUser.authentication;

    // Firebase only needs the ID token to create the credential
    final credential = GoogleAuthProvider.credential(
      idToken: googleAuth.idToken,
    );
    return FirebaseAuth.instance.signInWithCredential(credential);
  } on GoogleSignInException catch (e) {
    if (e.code == GoogleSignInExceptionCode.canceled) {
      throw Exception('Sign-in cancelled');
    }
    rethrow;
  }
}

このパターンはすべてのOAuthプロバイダに共通です。プロバイダのトークンを取得し、Firebaseの認証情報に包み、signInWithCredentialを呼び出します。アプリがGoogle API(Drive、Calendarなど)も呼び出す場合、アクセストークンは別のステップであるauthorizationClient.authorizeScopesから取得します。詳細はgoogle_sign_inの移行ガイドに記載されています。

面接の落とし穴:Google Sign-In v7

GoogleSignIn().signIn()と書き、authenticationからaccessTokenを読み取る候補者は、7.0より前のAPIを答えています。v7ではauthenticate()がアカウントを返し、authenticationが持つのはidTokenのみで、追加スコープのアクセストークンには明示的な認可呼び出しが必要です。

Firestoreのデータ操作とモデリング

Cloud Firestoreは、FlexibleでスケーラブルなNoSQLドキュメントデータベースです。コレクションとドキュメントの階層構造により、直感的なデータモデリングが可能です。以下は、タスク管理アプリケーションのCRUD操作を実装したサービスクラスの例です。

firestore_service.dartdart
import 'package:cloud_firestore/cloud_firestore.dart';

class TaskService {
  final _db = FirebaseFirestore.instance;
  final String _collection = 'tasks';

  // Create a new document with auto-generated ID
  Future<String> createTask(String userId, String title) async {
    final doc = await _db.collection(_collection).add({
      'userId': userId,
      'title': title,
      'completed': false,
      'createdAt': FieldValue.serverTimestamp(),
    });
    return doc.id;
  }

  // Read a single document by ID
  Future<Map<String, dynamic>?> getTask(String taskId) async {
    final snapshot = await _db.collection(_collection).doc(taskId).get();
    return snapshot.data();
  }

  // Update specific fields without overwriting the entire document
  Future<void> toggleComplete(String taskId, bool completed) async {
    await _db.collection(_collection).doc(taskId).update({
      'completed': completed,
      'updatedAt': FieldValue.serverTimestamp(),
    });
  }

  // Delete a document
  Future<void> deleteTask(String taskId) async {
    await _db.collection(_collection).doc(taskId).delete();
  }
}

FieldValue.serverTimestamp()を使用することで、クライアントの時刻ではなくFirebaseサーバーの時刻がタイムスタンプとして記録されます。これにより、クライアント間での時刻の不整合を防ぎ、データの一貫性を保つことができます。cloud_firestore 7.1.0からは、サーバー側の変換としてFieldValue.minimumとFieldValue.maximumも使えます。読み取りなしで、保存済みの値と送信された値のうち小さい方または大きい方を保持するため、ベストスコアのようなカウンターに向いています。

リアルタイムデータ同期

Firestoreの強力な機能の一つが、リアルタイムリスナーです。snapshots()メソッドを使用することで、データの変更を即座にUIに反映できます。

real_time_tasks.dartdart
import 'package:cloud_firestore/cloud_firestore.dart';

class TaskStream {
  final _db = FirebaseFirestore.instance;

  // Stream all tasks for a specific user, ordered by creation date
  Stream<List<Map<String, dynamic>>> userTasks(String userId) {
    return _db
        .collection('tasks')
        .where('userId', isEqualTo: userId)
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map((snapshot) => snapshot.docs.map((doc) {
              final data = doc.data();
              data['id'] = doc.id; // Include document ID
              return data;
            }).toList());
  }
}

このストリームをStreamBuilderと組み合わせることで、データベースの変更がリアルタイムでUIに反映される、リアクティブなアプリケーションを構築できます。複合インデックスが必要な場合は、Firestoreコンソールまたはfirestore.indexes.jsonで設定します。

Flutterの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Firestoreセキュリティルール

Firestoreのセキュリティルールは、データベースへのアクセスを制御する最も重要な防御層です。クライアントサイドのコードは簡単に改ざんできるため、サーバーサイドでのルール設定が不可欠です。

firestore.rulesjavascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Users can only access their own profile
    match /users/{userId} {
      allow read, update: if request.auth != null
                          && request.auth.uid == userId;
      allow create: if request.auth != null;
      allow delete: if false; // Prevent self-deletion
    }

    // Tasks belong to the user who created them
    match /tasks/{taskId} {
      allow read, write: if request.auth != null
                         && resource.data.userId == request.auth.uid;
      allow create: if request.auth != null
                    && request.resource.data.userId == request.auth.uid;
    }
  }
}

このルール設定では、認証済みユーザーのみがデータにアクセスでき、さらに自分が所有するデータのみを読み書きできるように制限しています。resource.dataは既存のドキュメントデータを、request.resource.dataは書き込み対象のデータを参照します。

セキュリティルールの重要性

本番環境では、allow read, write: if trueのようなオープンなルールは絶対に使用しないでください。これはデータベース全体を公開することを意味し、重大なセキュリティリスクとなります。Firebase App Checkと組み合わせて、不正なクライアントからのアクセスも防止することを推奨します。

オフライン対応とキャッシュ戦略

Firestoreは、デフォルトでオフライン永続化をサポートしています。ネットワーク接続が失われた場合でも、ローカルキャッシュからデータを読み取り、オンラインに復帰した際に自動的に同期されます。これにより、ユーザーは常にアプリケーションを使用できる状態が維持されます。

以下は、書き込みがまだサーバーに同期されていない状態を検出し、UIで表示する例です。

offline_aware_widget.dartdart
StreamBuilder<DocumentSnapshot>(
  stream: FirebaseFirestore.instance
      .collection('tasks')
      .doc(taskId)
      .snapshots(),
  builder: (context, snapshot) {
    if (!snapshot.hasData) return const CircularProgressIndicator();

    final data = snapshot.data!;
    final isPending = data.metadata.hasPendingWrites;

    return Row(
      children: [
        Text(data['title']),
        if (isPending) const Icon(Icons.cloud_upload, size: 16),
      ],
    );
  },
)

metadata.hasPendingWritesプロパティを確認することで、ローカルの変更がサーバーに同期されているかどうかを判定できます。これにより、ユーザーにデータの同期状態を視覚的にフィードバックすることが可能です。

本番アーキテクチャ:Repositoryパターン

大規模なアプリケーションでは、データアクセス層を抽象化するRepositoryパターンの採用が推奨されます。これにより、Firebaseへの依存を隔離し、テストの容易性と将来の変更への柔軟性を確保できます。

task_repository.dartdart
abstract class TaskRepository {
  Future<String> create(String userId, String title);
  Stream<List<Task>> watchAll(String userId);
  Future<void> update(String id, Map<String, dynamic> fields);
  Future<void> delete(String id);
}

// firebase_task_repository.dart
class FirebaseTaskRepository implements TaskRepository {
  final _db = FirebaseFirestore.instance;

  
  Future<String> create(String userId, String title) async {
    final doc = await _db.collection('tasks').add({
      'userId': userId,
      'title': title,
      'completed': false,
      'createdAt': FieldValue.serverTimestamp(),
    });
    return doc.id;
  }

  
  Stream<List<Task>> watchAll(String userId) {
    return _db
        .collection('tasks')
        .where('userId', isEqualTo: userId)
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map((s) => s.docs.map(Task.fromFirestore).toList());
  }

  // ... update and delete implementations
}

抽象クラスTaskRepositoryを定義し、FirebaseTaskRepositoryで具体的な実装を提供することで、依存性注入を通じてモックリポジトリに差し替えることができます。これにより、ユニットテストでFirebaseに接続することなくビジネスロジックをテストできます。RiverpodやBLoCなどの状態管理も、Firestoreを意識せずにこのインターフェースだけを利用します。

FlutterFire 5へのアップグレードで壊れるもの

2026年10月のメジャーリリース(BoM 5.0.0、firebase_auth 7.0.0、cloud_firestore 7.0.0、firebase_core 5.0.0)は、DartのAPIよりもビルド要件を大きく変えています。ビルドやコードレビューで引っかかるのは次の点です。

  • Android minSdk 24:プラグインはFirebase Android BoM 35をベースにしています。minSdkが21や23のままのプロジェクトはコンパイルできなくなります。
  • Apple SDK 13:Firebase Apple SDKはSwift Package Managerで13.0.0(CocoaPodsでは12.19.0)に上がり、SwiftパッケージにはmacOS 11が必要です。
  • App Check:activateのパラメータ名がproviderWeb、providerAndroid、providerAppleに変更されました。
  • 非推奨化:cloud_firestore 7.0.0でsetIndexConfigurationFromJSONが非推奨になり、firebase_ml_model_downloaderは公開が終了しました。
  • 6.x系ですでに削除済み:fetchSignInMethodsForEmail()とUser.updateEmail()。サインイン前にメールアドレスがどのプロバイダを使っているかを確認するコードは設計し直す必要があります。メール列挙保護によって、その情報は返されなくなっているためです。

パッケージごとの変更点の全体は、FlutterFireリリースノートとcloud_firestoreの変更履歴に掲載されています。

Flutter開発者向けFirebase面接質問

面接では、Firebaseサービスの概念的な理解と実装の両方が問われます。以下は、2026年のFlutter開発者面接で繰り返し出題される質問です。フレームワーク側の質問はFlutter面接質問のガイドで扱っています。

authStateChanges()はidTokenChanges()やuserChanges()と何が違いますか?

authStateChanges()はサインインとサインアウトのイベントでのみ発火します。idTokenChanges()はそれに加えて、IDトークンの更新時(およそ1時間ごと)にも発火します。userChanges()はそのすべてに加え、updateDisplayName()のようなプロフィール更新も捕捉します。画面遷移のガードには、ほとんどの場合authStateChanges()で足ります。

デバイスがオフラインのときにFirestoreへの書き込みを行うと、何が起きますか?

書き込みはローカルキャッシュと保留中の書き込みキューに保存されます。snapshots()のリスナーは、metadata.hasPendingWrites == trueの状態で更新後のデータを即座に受け取ります。接続が戻るとFirestoreは保留中の書き込みを自動的に同期し、最後にサーバーに届いた書き込みが優先されます。

チャットアプリのデータはFirestoreでどうモデリングすべきですか?

各ドキュメントにメタデータ(参加者、最新メッセージのタイムスタンプ)を持つchatsコレクションを用意し、各チャットドキュメントの下にmessagesサブコレクションを置きます。この構造により、ユーザーのチャット一覧を効率よく取得しつつ、会話ごとにメッセージをページネーションできます。一覧画面でサブコレクションを読まずに済むよう、最新メッセージは親ドキュメントに非正規化して保持します。

DateTime.now()よりFieldValue.serverTimestamp()を使うべき理由は何ですか?

DateTime.now()はデバイスの時計を使うため、不正確だったり改ざんされていたりする可能性があります。FieldValue.serverTimestamp()はFirestoreサーバーの時計を使うので、すべてのクライアントで一貫した順序が保証されます。メッセージの並び順、アクティビティフィード、監査ログなどで重要になります。

Firestoreで複合クエリはどう扱われ、インデックスの要件は何ですか?

whereとorderByで複数のフィールドを組み合わせるクエリには、複合インデックスが必要です。単一フィールドのインデックスは自動で作成されますが、複合インデックスはfirestore.indexes.jsonまたはFirebaseコンソールで手動で定義する必要があります。クエリが失敗すると、Firestoreは不足しているインデックスを作成するための直接リンクをログに出力します。

バッチ書き込みではなくトランザクションを使うべきなのはどんなときですか?

同じ操作の中で読み取ったデータに書き込みが依存する場合はトランザクションが必要です。たとえば、在庫が0より多いときだけ在庫を減らすケースです。読み取ったドキュメントがコミット前に変更されるとSDKはコールバックをリトライするため、コールバックに副作用を持たせてはいけません。バッチ書き込みは読み取りなしで複数の書き込みをアトミックに実行します。ドキュメントを作成しつつ別の場所のカウンターを更新する、といった用途です。オフライン時の挙動も異なり、バッチはキューに積まれますが、トランザクションは接続がなければ失敗します。

dart
final db = FirebaseFirestore.instance;

// Transaction: read then write, retried on contention
await db.runTransaction((tx) async {
  final ref = db.collection('products').doc(id);
  final snap = await tx.get(ref);
  final stock = snap.get('stock') as int;
  if (stock <= 0) throw Exception('Out of stock');
  tx.update(ref, {'stock': stock - 1});
});

// Batched write: atomic writes, no reads
final batch = db.batch();
batch.set(db.collection('orders').doc(), {'productId': id});
batch.update(db.collection('stats').doc('orders'), {
  'count': FieldValue.increment(1),
});
await batch.commit();

Firebaseで管理者ロールはどう実装しますか?

Admin SDKを通じて信頼できる環境からカスタムクレームを設定します。Flutterクライアントから設定することはありません。クレームはIDトークンの中に含まれるため、ルールは追加のドキュメント参照なしにrequest.auth.token.admin == trueとして読み取れます。カスタムクレームのドキュメントによるとペイロードの上限は1000バイトで、クライアントが新しいクレームを認識するのはトークン更新後です。更新はcurrentUser.getIdToken(true)で強制できます。

セキュリティルールとApp Checkの違いは何ですか?

セキュリティルールは、特定のユーザーが特定のドキュメントを読み書きできるかどうかを判断します。App Checkは、リクエストが正規のデバイス上の正規のアプリから来ていることを証明し、公開APIキーを使い回すスクリプトを遮断します。答えている問いが異なるため、本番アプリには両方が必要です。FlutterFire 5以降、App CheckはproviderAndroid、providerApple、providerWebで有効化します。

Firestoreの画面が高コストになる理由と、ページネーションの方法は?

Firestoreはドキュメントの読み取り、書き込み、削除の単位で課金されます。上限のないコレクションに対するリスナーは、初回ロードで条件に合うすべてのドキュメントを読み取り、その後は変更されたドキュメントごとに1回の読み取りが発生します。ページネーションにはオフセットではなくクエリカーソルを使います。最初のページはlimit(20)、次のページはstartAfterDocument(lastDoc)とすることで、各ページのコストは返されるドキュメント分だけになります。

FirestoreとRealtime Database、どちらをなぜ選びますか?

Firestoreはドキュメントをコレクションに格納し、インデックスによる複合クエリをサポートし、操作単位で課金されます。Realtime Databaseは単一のJSONツリーにデータを保存し、1つの子キーに対する浅いクエリのみを提供し、主に帯域幅とストレージで課金されます。そのため、プレゼンス表示のような小さく非常に頻繁な更新に向いています。新規のFlutterアプリの多くはFirestoreから始め、プレゼンス用途に限ってRealtime Databaseを追加します。

さらにFlutterの面接対策を進めるなら、専用の練習モジュールでこれらのテーマをインタラクティブなクイズと時間制限付きの課題で確認できます。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

参考資料

面接に向けたFlutter×Firebaseの要点

  • FlutterFire BoM 5.1.0はfirebase_auth 7.0.1とcloud_firestore 7.1.0を含み、AndroidのminSdk 24を必要とします
  • google_sign_in 7ではGoogleSignIn().signIn()がGoogleSignIn.instance.initialize()とauthenticate()に置き換わり、Firebaseに必要なのはidTokenだけです
  • authStateChanges()はリアクティブなストリームを提供し、手動チェックなしでアプリ全体のログイン状態を管理できます
  • snapshots()によるリアルタイム同期はポーリングを不要にし、StreamBuilderと直接組み合わせてUIを自動更新できます
  • 本番環境ではセキュリティルールが必須です。request.auth.uidをドキュメントの所有者フィールドと必ず照合し、ロールにはカスタムクレームを使います
  • トランザクションはリトライ付きで読み取ってから書き込み、バッチ書き込みは書き込みのみを行います。ドキュメントの読み取りはすべて課金されるため、ページネーションにはカーソルを使います
  • モバイルではオフライン永続化が追加コードなしで動作します。同期状態をユーザーに伝えるにはhasPendingWritesを使います
  • RepositoryパターンはFirebaseをビジネスロジックから切り離し、テストや将来の移行を容易にします
今日のチャレンジ

Flutter のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年11月9日 更新

共有

関連記事