Règles de sécurité Firebase après le mode test : pourquoi auth != null ne suffit pas
Le mode test de Firebase laisse n'importe qui lire et écraser ta base de données, et il n'est prévu que pour les premiers jours d'un projet. Quand il prend fin, les correctifs rapides habituels sont if true, qui rouvre tout, ou if request.auth != null, qui permet à n'importe quel utilisateur connecté de lire et de modifier les données de tous les autres. Le vrai remplaçant, c'est une règle qui compare l'ID de l'utilisateur connecté à un champ propriétaire sur chaque document, plus un test qui prouve qu'un deuxième compte ne peut pas entrer.
Ce qu'est le mode test, et ce qui se passe quand il prend fin
Quand tu crées une base Cloud Firestore, Firebase te demande de choisir entre deux configurations de départ. Sa documentation présente le mode test comme pratique pour faire ses premiers pas avec les bibliothèques client, mais comme un mode qui permet à n'importe qui de lire et d'écraser tes données. Le mode production fait l'inverse : il refuse toutes les lectures et écritures venant des clients mobiles et web.
Le mode test est temporaire par conception. Pour la Realtime Database, la documentation détaille comment il se termine : si tu gardes les règles par défaut, lisibles et modifiables par tout le monde, au-delà d'une période d'essai, Firebase t'envoie un e-mail, puis tes règles refusent toutes les requêtes.
C'est à cette dernière étape que la plupart des apps cassent, souvent des semaines après le lancement. L'app ne charge plus de données, quelqu'un ouvre l'onglet Rules dans l'urgence, et l'une de deux lignes y atterrit.
Les deux correctifs rapides qui ne protègent rien
La première :
allow read, write: if true;
La page de Firebase consacrée à la correction des règles non sécurisées classe cette ligne dans l'accès ouvert, le schéma qui permet à n'importe qui d'écraser toute ta base de données. C'est le mode test, sans la date d'expiration.
La seconde a l'air responsable :
allow read, write: if request.auth != null;
Elle vérifie qu'une requête vient de quelqu'un de connecté. Elle ne dit rien des documents que cette personne devrait voir. La même page de Firebase te demande de confirmer que tu veux vraiment que n'importe quel utilisateur connecté ait accès aux données avant de te reposer sur cette seule vérification.
Pourquoi « connecté » est la mauvaise question
Ta configuration Firebase, clé d'API comprise, fait partie de ton app client. La documentation de Firebase affirme que ce n'est pas un problème : les clés limitées aux services Firebase n'ont pas besoin d'être traitées comme des secrets, parce que les données sont protégées par les Security Rules et App Check, pas par le fait de cacher la clé.
La conséquence passe facilement inaperçue. Si l'inscription par e-mail et mot de passe ou la connexion anonyme est activée, n'importe qui peut prendre ta configuration publique, créer un compte depuis la console d'un navigateur ou un petit script, et devenir un utilisateur authentifié. À partir de là, request.auth != null est vrai pour cette personne sur chaque document de chaque collection couverte par tes règles.
La question à laquelle une règle doit répondre n'est donc pas « quelqu'un est-il connecté ? », mais « ce document appartient-il à la personne qui le demande ? ».
La même page signale un piège voisin : une règle qui fait confiance à un domaine d'e-mail, par exemple en n'acceptant que les adresses qui se terminent par @example.com, n'est pas sûre si elle n'exige pas aussi un e-mail vérifié. Sans cette vérification, n'importe qui peut se connecter avec une adresse arbitraire sur ton domaine.
Une règle Firestore réservée au propriétaire
Prends une collection notes où chaque document stocke l'ID de l'utilisateur qui l'a créé dans owner_uid. Cet ensemble de règles, adapté du schéma « propriétaire du contenu » de Firebase, traite chaque opération séparément :
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /notes/{noteId} {
allow read, delete: if request.auth != null
&& request.auth.uid == resource.data.owner_uid;
allow create: if request.auth != null
&& request.auth.uid == request.resource.data.owner_uid;
allow update: if request.auth != null
&& request.auth.uid == resource.data.owner_uid
&& request.resource.data.owner_uid == resource.data.owner_uid;
}
}
}
Quelques détails font tout le travail ici :
resource.dataest le document tel qu'il existe maintenant ;request.resource.dataest le document tel qu'il serait après l'écriture.- Sur
create, l'utilisateur ne peut créer qu'une note qui le désigne lui-même comme propriétaire. - Sur
update, la dernière condition empêche un utilisateur de transférer sa note vers le compte de quelqu'un d'autre, ou de s'approprier la note d'un autre en changeant le champ.
Tout ce qui ne correspond à aucune règle est refusé : les collections pour lesquelles tu n'as pas écrit de règles restent fermées.
Les règles ne sont pas des filtres : corrige aussi tes requêtes
Après le passage à des règles réservées au propriétaire, une surprise fréquente est que les écrans de liste ne fonctionnent plus. Firestore évalue une requête par rapport à tout ce qu'elle pourrait renvoyer, pas par rapport aux documents qui se trouvent exister. Comme le dit la documentation, les règles de sécurité ne sont pas des filtres, et une requête passe en entier ou pas du tout.
Une requête sur toute la collection échoue, même si chaque note qu'elle contient appartient à l'utilisateur courant :
db.collection('notes').get(); // denied
La requête doit porter la même contrainte que la règle :
db.collection('notes').where('owner_uid', '==', user.uid).get(); // allowed
Pour des données partagées, comme des notes visibles par tous les membres d'une équipe, stocke les ID des membres dans un tableau et applique la même idée aux deux endroits : request.auth.uid in resource.data.members dans la règle, et where('members', 'array-contains', user.uid) dans la requête. Qui peut ajouter ou retirer des membres demande alors sa propre règle d'update.
Teste les règles avant de les déployer
Dans la console. Le Rules Playground de la console Firebase simule une requête contre ton brouillon de règles. Choisis une requête authentifiée, saisis un UID qui n'est pas celui du propriétaire, et essaie de lire un de tes documents de test. Elle doit être refusée. Recommence ensuite avec l'UID du propriétaire.
Dans le code. La Local Emulator Suite, avec @firebase/rules-unit-testing, transforme ces mêmes vérifications en tests qui tournent à chaque modification :
import { readFileSync } from 'node:fs';
import { initializeTestEnvironment, assertFails, assertSucceeds } from '@firebase/rules-unit-testing';
const env = await initializeTestEnvironment({
projectId: 'demo-notes',
firestore: { rules: readFileSync('firestore.rules', 'utf8') },
});
await env.withSecurityRulesDisabled(async (ctx) => {
await ctx.firestore().doc('notes/n1').set({ owner_uid: 'alice', text: 'draft' });
});
const alice = env.authenticatedContext('alice').firestore();
const mallory = env.authenticatedContext('mallory').firestore();
await assertSucceeds(alice.doc('notes/n1').get());
await assertFails(mallory.doc('notes/n1').get());
await assertFails(mallory.collection('notes').add({ owner_uid: 'alice', text: 'x' }));
await assertFails(alice.doc('notes/n1').update({ owner_uid: 'mallory' }));
await env.cleanup();
Lance-les contre l'émulateur avec :
firebase emulators:exec --only firestore "npm test"
Firebase recommande un ID de projet qui commence par demo- pour les tests, pour que rien ne puisse toucher un vrai projet.
Si ton app a aussi un front-end web construit sur un autre backend, la même idée des deux comptes s'y applique, comme dans cette vérification de la row level security de Supabase.
Ce que les règles ne couvrent pas
Les Security Rules s'appliquent aux requêtes des SDK client mobiles et web. La documentation de Firebase précise que, même en mode production, tes serveurs d'application authentifiés peuvent toujours accéder à la base, et que les bibliothèques client côté serveur contournent toutes les Security Rules de Cloud Firestore. Toute Cloud Function ou route backend qui lit des données pour le compte d'un utilisateur a besoin de sa propre vérification de propriété dans le code.
FAQ
Que se passe-t-il quand le mode test de Firebase expire ?
La documentation de Firebase le décrit pour la Realtime Database : tu reçois un e-mail, puis les règles refusent toutes les requêtes, et l'app ne peut plus ni lire ni écrire de données. Remplace-les par des règles limitées à chaque utilisateur plutôt que de rouvrir l'accès.
« allow read, write: if request.auth != null » est-il sécurisé ?
Seulement si chaque utilisateur connecté est censé voir et modifier toutes les données. Comme n'importe qui peut créer un compte quand l'inscription publique est activée, cette règle expose en général les documents de chaque utilisateur à tous les autres.
Est-ce sûr d'avoir ma clé d'API Firebase dans le client ?
Oui, pour les clés limitées aux services Firebase. La documentation de Firebase dit que les données sont protégées par les Security Rules et App Check, pas par le secret de cette clé. Les clés d'autres API, comme la Gemini Developer API, ne doivent jamais se retrouver dans le code client.
Sources
- Firebase, Create and manage Cloud Firestore databases (test and production mode)
- Firebase, Realtime Database: get started (trial period)
- Firebase, Fix insecure rules
- Firebase, Learn about using and managing API keys
- Firebase, Securely query data (rules are not filters)
- Firebase, Rules Playground
- Firebase, Build unit tests