Questions d'entretien Firebase 2026 : authentification Flutter, Firestore et règles de sécurité

Questions d'entretien Firebase pour développeurs Flutter, avec réponses en Dart : flux d'authentification, Google Sign-In v7, CRUD et modélisation Firestore, règles de sécurité, custom claims, transactions et changements de FlutterFire 5.

Questions d'entretien Firebase 2026 : authentification Flutter, Firestore et règles de sécurité

Les questions d'entretien Firebase pour un poste Flutter tournent autour de cinq sujets : les trois flux d'état d'authentification, la modélisation des données Firestore, les règles de sécurité, les écritures hors ligne et le modèle de coût derrière chaque lecture. Firebase reste le backend par défaut des applications Flutter en 2026, et depuis octobre 2026 les plugins FlutterFire sont publiés sous le BoM 5.x (firebase_core 5.1.0, firebase_auth 7.0.1, cloud_firestore 7.1.0), avec un minSdk Android relevé à 24 et le SDK Firebase Apple 13 en dessous.

FlutterFire BoM 5.1.0 (octobre 2026)

La version actuelle de FlutterFire est le BoM 5.1.0 : firebase_auth 7.0.1, cloud_firestore 7.1.0, firebase_core 5.1.0. La version majeure 5.0.0 impose un minSdk Android 24, s'appuie sur le SDK Firebase Apple 13.0.0 et renomme les paramètres de activate dans App Check. Les Firestore Pipelines, arrivées avec cloud_firestore 6.3.0, gagnent de nouvelles expressions à chaque version.

Questions d'entretien Firebase : ce qui est demandé en 2026

Les recruteurs posent rarement des questions de mémoire sur l'API. Ils vérifient si le candidat sait où Firebase applique les règles, ce que coûte chaque opération et ce qui se passe quand le réseau tombe. Le tableau ci-dessous associe les sujets récurrents à ce qu'une bonne réponse doit contenir. Chacun est traité en détail dans la section questions d'entretien Flutter Firebase plus bas.

SujetQuestion typeCe que contient une bonne réponse
Flux d'authentificationauthStateChanges() vs idTokenChanges() vs userChanges()Les événements émis par chacun, et celui qui protège la navigation
SécuritéOù le contrôle d'accès est-il appliqué ?Les règles de sécurité côté serveur, jamais des vérifications côté client ; App Check est une autre couche
RôlesComment implémenter un rôle admin ?Des custom claims posés par l'Admin SDK, lus via request.auth.token
ModélisationModéliser une application de chatSous-collections, dernier message dénormalisé, un document par message
CohérenceTransaction ou écriture groupée ?La transaction lit puis écrit et se relance ; le batch ne fait qu'écrire, de façon atomique
Hors ligneQue devient une écriture hors ligne ?Cache local, hasPendingWrites, synchronisation à la reconnexion
CoûtPourquoi cet écran coûte-t-il cher ?Facturation par document lu, listeners, pagination par curseurs
ComparaisonFirestore vs Realtime DatabaseModèle de requête, montée en charge, unité de facturation

Configuration de Firebase dans un Projet Flutter

La configuration de Firebase s'effectue désormais en une seule commande grâce à l'outil FlutterFire CLI. Après avoir créé un projet Firebase dans la console, l'initialisation dans l'application Flutter nécessite l'ajout des dépendances firebase_core et firebase_auth dans le fichier pubspec.yaml, puis l'exécution de flutterfire configure pour générer automatiquement les fichiers de configuration spécifiques à chaque plateforme.

L'initialisation de Firebase doit impérativement se faire avant le lancement de l'application, dans la fonction main(). Le SDK charge les options de configuration adaptées à la plateforme cible (iOS, Android, Web) via le fichier généré firebase_options.dart.

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());
}

Cette approche garantit que les services Firebase sont prêts avant le premier rendu de l'arbre de widgets, évitant ainsi les erreurs d'initialisation tardive.

Authentification Firebase : Email, Google et Multi-Facteurs

Le package firebase_auth offre une gamme complète de méthodes d'authentification, de l'inscription par email/mot de passe aux fournisseurs OAuth tiers comme Google, Apple et Microsoft. L'authentification multi-facteurs inclut TOTP sur macOS depuis firebase_auth 6.1.0. La gestion des erreurs spécifiques de Firebase Auth permet d'afficher des messages utilisateur pertinents selon le contexte d'échec.

Inscription et Connexion par Email et Mot de Passe

L'authentification par email constitue le socle de base pour la majorité des applications. La création d'un service d'authentification centralisé facilite la réutilisation et les tests unitaires. Les exceptions FirebaseAuthException fournissent des codes d'erreur spécifiques (email-already-in-use, weak-password, user-not-found) qui permettent d'adapter les messages d'erreur présentés à l'utilisateur.

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();
}

Le flux authStateChanges() constitue un élément central de l'architecture réactive : il permet de reconstruire automatiquement l'interface utilisateur lorsque l'état d'authentification change, simplifiant la gestion des redirections entre écrans publics et protégés.

Les règles de mot de passe n'ont plus à vivre uniquement côté client : l'API validatePassword (disponible depuis firebase_auth 6.0.0) vérifie un mot de passe par rapport à la politique configurée dans la console Firebase, qui peut exiger une longueur minimale, des majuscules, des minuscules, des chiffres et des caractères spéciaux.

Intégration de la Connexion Google

Les fournisseurs OAuth nécessitent un package supplémentaire. Depuis google_sign_in 7.0, le plugin est un singleton obtenu via GoogleSignIn.instance, il doit être initialisé une seule fois, et l'authentification est séparée de l'autorisation. L'appel GoogleSignIn().signIn() des anciens tutoriels ne compile plus.

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;
  }
}

Ce schéma vaut pour tous les fournisseurs OAuth : obtenir le jeton du fournisseur, l'envelopper dans un credential Firebase, puis appeler signInWithCredential. Quand l'application doit aussi appeler une API Google (Drive, Calendar), le jeton d'accès s'obtient désormais dans une étape distincte, authorizationClient.authorizeScopes, comme le décrit le guide de migration de google_sign_in.

Piège d'entretien : Google Sign-In v7

Un candidat qui écrit GoogleSignIn().signIn() et lit accessToken dans authentication cite l'API antérieure à la 7.0. En v7, authenticate() renvoie le compte, authentication ne porte que l'idToken, et les jetons d'accès pour des scopes supplémentaires exigent un appel d'autorisation explicite.

Opérations CRUD et Modélisation des Données dans Firestore

Firestore organise les données en collections de documents. Chaque document contient des paires clé-valeur et peut référencer des sous-collections imbriquées. Les opérations CRUD (Create, Read, Update, Delete) s'effectuent via des méthodes asynchrones qui retournent des Future ou des Stream pour les écoutes temps réel.

Écriture et Lecture de Documents

La méthode add() génère automatiquement un identifiant unique pour chaque nouveau document, tandis que set() permet de spécifier manuellement l'ID. L'utilisation de FieldValue.serverTimestamp() garantit que les timestamps sont cohérents côté serveur, indépendamment de l'horloge locale de l'appareil.

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();
  }
}

La méthode update() modifie uniquement les champs spécifiés sans écraser les autres propriétés du document, contrairement à set() qui remplace l'intégralité du contenu.

Depuis cloud_firestore 7.1.0, FieldValue.minimum et FieldValue.maximum rejoignent FieldValue.serverTimestamp() parmi les transformations côté serveur : elles conservent la plus petite ou la plus grande des deux valeurs, stockée et entrante, sans lecture préalable, ce qui convient aux compteurs comme un meilleur score.

Flux Temps Réel avec snapshots()

La force de Firestore réside dans sa capacité à diffuser les modifications de données en temps réel vers les clients. La méthode snapshots() retourne un Stream qui émet un nouvel événement à chaque modification de la requête surveillée. Cette approche élimine le besoin de polling manuel et garantit que l'interface utilisateur reste synchronisée avec la base de données.

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());
  }
}

L'utilisation de StreamBuilder dans l'interface permet de reconstruire automatiquement les widgets lorsque de nouvelles données arrivent, créant une expérience utilisateur fluide et réactive.

Prêt à réussir tes entretiens Flutter ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Règles de Sécurité Firestore pour Applications Flutter

Les règles de sécurité Firestore constituent la seule barrière de protection entre les données et les clients non autorisés. Contrairement aux systèmes backend traditionnels où les vérifications d'autorisation s'effectuent côté serveur, Firestore délègue cette responsabilité à un système de règles déclaratives évaluées côté cloud avant chaque opération de lecture ou d'écriture.

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;
    }
  }
}
Sécurité des Règles Firestore

Les règles de sécurité par défaut (allow read, write: if false;) bloquent tout accès. Ne jamais déployer de règles permissives en production sans validation approfondie via l'émulateur Firestore et les tests unitaires de règles.

La distinction entre resource.data (données existantes) et request.resource.data (données entrantes) permet de valider les modifications avant leur persistance, par exemple pour empêcher un utilisateur de modifier le champ userId d'une tâche existante.

Persistence Hors Ligne et Stratégie de Cache

Firestore intègre nativement un système de cache local qui synchronise automatiquement les modifications lorsque la connexion réseau est rétablie. Sur mobile, cette fonctionnalité est activée par défaut et ne nécessite aucune configuration supplémentaire. Les métadonnées de snapshot permettent de détecter l'état de synchronisation.

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),
      ],
    );
  },
)

La propriété hasPendingWrites indique si les modifications locales n'ont pas encore été confirmées par le serveur, permettant d'afficher un indicateur visuel de synchronisation en cours. Cette approche offre une expérience utilisateur optimiste sans bloquer l'interface pendant les opérations réseau.

Structurer une Application Flutter Firebase de Production

L'architecture d'une application de production nécessite une séparation stricte entre la logique métier et les détails d'implémentation Firebase. Le pattern Repository abstrait les opérations de données derrière une interface, facilitant les tests unitaires et permettant de remplacer Firebase par un autre backend sans modifier le code métier.

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
}

Les solutions de gestion d'état comme Riverpod ou BLoC consomment alors l'interface du repository sans rien savoir de Firestore. Cette architecture permet d'injecter une implémentation mock du repository dans les tests, éliminant la dépendance au réseau et accélérant l'exécution des suites de tests. Les classes de modèle (Task) encapsulent la logique de sérialisation/désérialisation, isolant le reste de l'application des détails du format de stockage Firestore.

Migrer vers FlutterFire 5 : ce qui casse

La version majeure d'octobre 2026 (BoM 5.0.0, firebase_auth 7.0.0, cloud_firestore 7.0.0, firebase_core 5.0.0) change davantage les exigences de build que les API Dart. Les points qui font échouer un build ou une revue de code :

  • minSdk Android 24 : les plugins reposent sur le BoM Firebase Android 35. Un projet encore en minSdk 21 ou 23 ne compile plus.
  • SDK Apple 13 : le SDK Firebase Apple passe en 13.0.0 avec Swift Package Manager (12.19.0 avec CocoaPods), et les packages Swift exigent macOS 11.
  • App Check : les paramètres de activate sont renommés providerWeb, providerAndroid et providerApple.
  • Dépréciations : setIndexConfigurationFromJSON est déprécié dans cloud_firestore 7.0.0, et firebase_ml_model_downloader n'est plus publié.
  • Déjà supprimés dans la ligne 6.x : fetchSignInMethodsForEmail() et User.updateEmail(). Le code qui vérifie quel fournisseur utilise un email avant la connexion doit être repensé, car la protection contre l'énumération des emails masque cette information.

La liste complète par package figure dans les notes de version FlutterFire et le changelog de cloud_firestore.

Questions d'entretien Firebase pour développeurs Flutter

Les entretiens testent à la fois la compréhension des concepts et la mise en pratique des services Firebase. Les questions suivantes reviennent régulièrement dans les entretiens Flutter en 2026. Le guide plus large des questions d'entretien Flutter couvre le côté framework.

En quoi authStateChanges() diffère-t-il de idTokenChanges() et userChanges() ?

authStateChanges() n'émet qu'à la connexion et à la déconnexion. idTokenChanges() émet aussi au rafraîchissement du jeton ID (environ toutes les heures). userChanges() capte tout cela, plus les mises à jour de profil comme updateDisplayName(). Pour la plupart des gardes de navigation, authStateChanges() suffit.

Que se passe-t-il quand une écriture Firestore a lieu alors que l'appareil est hors ligne ?

L'écriture est stockée dans le cache local et dans une file d'écritures en attente. Les listeners snapshots() reçoivent immédiatement les données mises à jour avec metadata.hasPendingWrites == true. Au retour de la connexion, Firestore synchronise automatiquement les écritures en attente, et la dernière écriture arrivée au serveur l'emporte.

Comment modéliser les données Firestore d'une application de chat ?

Une collection chats dont chaque document contient les métadonnées (participants, horodatage du dernier message), et une sous-collection messages sous chaque document de chat. Cette structure permet de requêter efficacement la liste des conversations d'un utilisateur tout en paginant les messages de chaque conversation. Le dernier message est dénormalisé dans le document parent pour éviter de lire la sous-collection dans la vue liste.

Pourquoi préférer FieldValue.serverTimestamp() à DateTime.now() ?

DateTime.now() utilise l'horloge de l'appareil, qui peut être imprécise ou manipulée. FieldValue.serverTimestamp() utilise l'horloge du serveur Firestore, ce qui garantit un ordre cohérent entre tous les clients. C'est déterminant pour l'ordre des messages, les fils d'activité et les journaux d'audit.

Comment Firestore gère-t-il les requêtes composées, et quels index exigent-elles ?

Firestore exige un index composite pour les requêtes qui combinent plusieurs champs avec des clauses where et orderBy. Les index sur un seul champ sont créés automatiquement, mais les index composites doivent être définis à la main dans firestore.indexes.json ou depuis la console Firebase. Quand une requête échoue, Firestore journalise un lien direct pour créer l'index manquant.

Quand utiliser une transaction plutôt qu'une écriture groupée ?

Une transaction est nécessaire quand l'écriture dépend d'une donnée lue dans la même opération, par exemple décrémenter un stock seulement s'il est supérieur à zéro. Le SDK relance le callback si un document lu change avant le commit, le callback ne doit donc pas avoir d'effets de bord. Une écriture groupée (batch) effectue plusieurs écritures de façon atomique sans aucune lecture, par exemple créer un document et incrémenter un compteur ailleurs. Les deux ne se comportent pas de la même façon hors ligne : un batch est mis en file, une transaction échoue sans connexion.

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();

Comment implémenter un rôle admin avec Firebase ?

Avec des custom claims posés depuis un environnement de confiance via l'Admin SDK, jamais depuis le client Flutter. Le claim voyage dans le jeton ID, donc les règles le lisent avec request.auth.token.admin == true sans lecture de document supplémentaire. La documentation des custom claims plafonne la charge utile à 1000 octets, et le client ne voit un nouveau claim qu'après un rafraîchissement du jeton, que currentUser.getIdToken(true) force.

Quelle différence entre les règles de sécurité et App Check ?

Les règles de sécurité décident si un utilisateur donné peut lire ou écrire un document donné. App Check atteste que la requête provient de l'application authentique sur un appareil authentique, ce qui bloque les scripts qui réutilisent la clé d'API publique. Les deux répondent à des questions différentes, une application en production a donc besoin des deux. Depuis FlutterFire 5, App Check s'active avec providerAndroid, providerApple et providerWeb.

Pourquoi un écran Firestore peut-il coûter cher, et comment paginer ?

Firestore facture chaque lecture, écriture et suppression de document. Un listener sur une collection non bornée lit tous les documents correspondants au premier chargement, puis une lecture par document modifié. La pagination passe par des curseurs de requête plutôt que par des offsets : limit(20) pour la première page, puis startAfterDocument(lastDoc) pour la suivante, de sorte que chaque page ne coûte que les documents qu'elle renvoie.

Firestore ou Realtime Database : lequel choisir et pourquoi ?

Firestore stocke des documents dans des collections, prend en charge les requêtes composées avec index et facture à l'opération. La Realtime Database stocke un unique arbre JSON, propose des requêtes peu profondes sur une seule clé enfant et facture surtout la bande passante et le stockage, ce qui convient aux petites mises à jour très fréquentes comme les indicateurs de présence. La plupart des nouvelles applications Flutter démarrent sur Firestore et n'ajoutent la Realtime Database que pour la présence.

Pour aller plus loin dans la préparation aux entretiens Flutter, des modules d'entraînement dédiés couvrent ces sujets avec des quiz interactifs et des défis chronométrés.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Sources

L'essentiel de Flutter Firebase pour les entretiens

  • FlutterFire BoM 5.1.0 embarque firebase_auth 7.0.1 et cloud_firestore 7.1.0, et exige un minSdk Android 24
  • google_sign_in 7 remplace GoogleSignIn().signIn() par GoogleSignIn.instance.initialize() puis authenticate(), et Firebase n'a besoin que de l'idToken
  • authStateChanges() fournit un flux réactif pour gérer l'état de connexion dans toute l'application, sans vérification manuelle
  • La synchronisation temps réel via snapshots() supprime le polling et s'intègre directement à StreamBuilder pour mettre à jour l'interface
  • Les règles de sécurité sont obligatoires en production : toujours comparer request.auth.uid aux champs de propriété des documents, et passer par les custom claims pour les rôles
  • Les transactions lisent puis écrivent avec relances, les écritures groupées ne font qu'écrire ; la pagination utilise des curseurs parce que chaque document lu est facturé
  • La persistance hors ligne fonctionne sans code supplémentaire sur mobile ; hasPendingWrites indique à l'utilisateur l'état de synchronisation
  • Le pattern Repository découple Firebase de la logique métier, ce qui simplifie les tests et les migrations futures
Défi du jour

Tu saurais repérer le bug en Flutter ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 9 novembre 2026

Partager

Articles similaires