---
title: "Règles de sécurité Firebase : 11 règles attaquées"
description: "Règles de sécurité Firebase testées dans l’émulateur : ce que chaque règle laissait faire à un invité ou à un autre utilisateur, et celle qui a fermé la fuite."
url: "https://madsalt.tech/fr/blog/firebase-security-rules-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/fr/about/"
language: "fr"
published: 2026-11-03
---

# Règles de sécurité Firebase : 11 règles attaquées

En bref

J’ai envoyé les mêmes requêtes à 11 jeux de règles Firestore, en tant qu’invité, utilisateur anonyme et trois utilisateurs connectés. `request.auth != null` a laissé un inconnu lire, modifier et supprimer les commandes de tout le monde. Un contrôle du propriétaire sur `resource` seul a permis à un utilisateur de céder une commande à quelqu’un d’autre. Un seul `match` large `{document=**}` a ouvert une collection qui avait pourtant une règle de propriétaire correcte. Et un rôle stocké dans un document que l’utilisateur peut écrire l’a rendu admin en une seule requête. Chaque cas a sa correction ci-dessous, et la règle corrigée a tenu face aux mêmes requêtes.

Une application Firebase n’a aucun serveur à vous entre le navigateur et la base de données. La configuration du projet est livrée dans la page, et les [règles de sécurité](https://firebase.google.com/docs/rules/insecure-rules) sont tout ce qui sépare ceux qui la possèdent de vos données. Je voulais donc des exemples de règles avec l’attaque à côté, pas une liste de règles qui ont l’air correctes.

**Comment j’ai testé.** Firebase Local Emulator Suite de Firebase CLI 15.34.2, le 3 novembre 2026. J’ai chargé chaque jeu de règles, réinitialisé les données et appelé l’API REST de Firestore avec de vrais jetons de l’émulateur Auth : aucun jeton, un utilisateur anonyme, les utilisateurs A et B, et un inconnu qui venait seulement de s’inscrire. Les données étaient inventées : les commandes `a1` et `a2` appartenant à A, `b1` appartenant à B.

## Quelles règles Firebase fuient ?

| Règle                                           | Requête                                   | Résultat     |
| ----------------------------------------------- | ----------------------------------------- | ------------ |
| `if true` (mode test)                           | L’invité liste les commandes              | `200`, les 3 |
| `if true` (mode test)                           | L’invité supprime la commande de B        | `200`        |
| `request.auth != null`                          | L’utilisateur anonyme liste les commandes | `200`, les 3 |
| `request.auth != null`                          | L’inconnu supprime la commande de A       | `200`        |
| Contrôle du propriétaire sur `resource`         | A crée une commande appartenant à B       | `200`        |
| Contrôle du propriétaire sur `resource`         | A modifie `ownerId` de sa propre commande | `200`        |
| Règle propriétaire plus lecture `{document=**}` | A lit la commande de B                    | `200`        |
| L’utilisateur écrit son propre doc `users`      | A met `role: admin`, lit la commande de B | `200`        |

## Que ouvre vraiment le mode test ?

```
match /{document=**} {
  allow read, write: if true;
}
```

Tout, à tout le monde. Un invité sans jeton a listé les 3 commandes, en a supprimé une et en a créé une nouvelle. Rien de tout cela ne demande un compte ou un secret divulgué, seulement l’identifiant du projet.

La version datée que le mode test utilise souvent échoue autrement. Avec `if request.time < timestamp.date(2026, 1, 1)` et cette date dans le passé, l’invité a reçu `403`, et l’utilisateur A aussi en lisant sa propre commande. La fuite est terminée, et l’application aussi.

## `request.auth != null` suffit-il ?

Non. Cela dit seulement qu’un compte quelconque se trouve derrière la requête.

```
match /orders/{id} {
  allow read, write: if request.auth != null;
}
```

L’invité a reçu `403`, ce qui explique pourquoi cette règle paraît sûre dans un test rapide. Puis un inconnu qui venait de s’inscrire a listé les 3 commandes, changé le total de celle de B et supprimé celle de A, chaque fois avec `200`. Un utilisateur anonyme a lui aussi listé les 3. Si l’inscription est ouverte, cette règle signifie « tous ceux qui se donnent la peine de s’inscrire ». La documentation de Firebase la cite aussi parmi les [schémas dangereux courants](https://firebase.google.com/docs/rules/insecure-rules).

## Pourquoi un contrôle du propriétaire doit-il porter sur `resource` et `request.resource` ?

`resource` est le document tel qu’il est stocké. `request.resource` est le document tel qu’il serait après l’écriture. Une règle qui ne regarde que le premier vérifie à qui appartient la ligne maintenant, et ne dit rien de ce qu’on en fait.

```
match /orders/{id} {
  allow read, update, delete: if resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null;
}
```

Les lectures semblaient correctes : A a reçu `200` sur sa commande et `403` sur celle de B. Pas les écritures. A a créé une commande avec `ownerId` réglé sur B, et a reçu `200`. A a ensuite changé `ownerId` de sa propre commande pour B, également `200`, et B pouvait la lire à partir de là. Dans une application où les commandes sont des factures ou des soldes, c’est un utilisateur qui écrit dans le compte de quelqu’un d’autre.

Cette version a tout refusé :

```
match /orders/{id} {
  allow read, delete: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null
    && request.resource.data.ownerId == request.auth.uid;
  allow update: if request.auth != null
    && resource.data.ownerId == request.auth.uid
    && request.resource.data.ownerId == resource.data.ownerId;
}
```

| Requête de A                         | Résultat |
| ------------------------------------ | -------- |
| Lire sa propre commande              | `200`    |
| Lire, modifier, supprimer celle de B | `403`    |
| Créer une commande appartenant à B   | `403`    |
| Changer `ownerId` pour B             | `403`    |

A pouvait encore modifier le `total` de sa propre commande, avec `200`. Si le client ne doit pas fixer ce champ, dites quels champs il peut modifier :

```
allow update: if request.auth != null
  && resource.data.ownerId == request.auth.uid
  && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
```

Avec cette règle, modifier `total` a renvoyé `403` et modifier `note` a renvoyé `200`.

## Pourquoi ma requête a-t-elle échoué avec une règle correcte ?

Parce que [les règles ne sont pas des filtres](https://firebase.google.com/docs/firestore/security/rules-query). Avec la règle de propriétaire ci-dessus, A qui demandait toute la collection a reçu `403`, alors qu’il possède 2 des 3 commandes. Firestore juge la requête d’après ce qu’elle pourrait renvoyer, pas d’après ce qui existe.

| Requête de A           | Résultat      |
| ---------------------- | ------------- |
| Lister les commandes   | `403`         |
| Requête `ownerId == A` | `200`, 2 docs |
| Requête `ownerId == B` | `403`         |

Donc quand une liste casse après un durcissement des règles, corrigez la requête. Assouplir la règle pour que la liste marche, c’est ainsi que s’écrivent les règles trop larges vues plus haut.

## Un `match` large peut-il annuler une règle correcte ?

Oui, et c’est le premier point que je chercherais dans un projet avec un long fichier de règles. Firestore [autorise une requête si une seule règle correspondante l’autorise](https://firebase.google.com/docs/firestore/security/rules-structure).

```
match /orders/{id} {
  allow read: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
}
match /{document=**} {
  allow read: if request.auth != null;
}
```

Le premier bloc ressemble à une bonne règle de propriétaire. Avec le second en place, A a lu la commande de B avec `200`, un inconnu a listé les 3 commandes, et le même inconnu a lu le document de A dans `users`. Rien dans le premier bloc ne vous avertit. Supprimez le `match` large ; aucune règle placée plus bas ne peut reprendre l’accès.

## Comment un utilisateur se rend-il admin ?

Ce schéma apparaît dès que les rôles vivent dans un document :

```
function isAdmin() {
  return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
match /users/{uid} {
  allow read, write: if request.auth.uid == uid;
}
match /orders/{id} {
  allow read: if resource.data.ownerId == request.auth.uid || isAdmin();
}
```

« Les utilisateurs peuvent modifier leur propre profil » et « les admins peuvent lire toutes les commandes » sont chacune raisonnables. Ensemble, elles forment une élévation de privilèges.

![Admin en une requête. Utilisateur A, dont le document users est modifiable. Avant: Utilisateur A → lire la commande de B → 403; Écrire son rôle: Utilisateur A → role: admin → 200; Après: Utilisateur A → lire la commande de B → 200.](https://madsalt.tech/charts/fr/firebase-security-rules-examples.svg)

*Figure 1. Mon test dans l’émulateur Firebase (CLI 15.34.2), 3 novembre 2026.*

La correction est d’empêcher le client d’écrire ce champ. Avec `request.resource.data.role == resource.data.role` ajouté à la règle d’écriture, la même requête a renvoyé `403` et A est resté hors de la commande de B. Les custom claims posés depuis un serveur sont l’autre solution, puisqu’un client ne peut pas les modifier.

## Deux choses qui se sont avérées correctes

Les règles d’un parent ne fuient pas dans les sous-collections. Avec `allow read` sur `/orders/{id}` pour tout utilisateur connecté, A a lu la commande, et `/orders/b1/notes/n1` en dessous a renvoyé `403`. C’est le [comportement documenté](https://firebase.google.com/docs/firestore/security/rules-structure) : les règles ne s’appliquent qu’au chemin qui correspond.

Un invité ne passe pas à travers une comparaison avec un jeton absent. `resource.data.ownerId == request.auth.uid` a renvoyé `403` pour l’invité, même sans `request.auth != null` séparé. J’écris quand même le contrôle, parce que la règle dit alors ce qu’elle veut dire.

## Comment vérifier vos propres règles ?

Lancez vos requêtes dans l’émulateur en tant qu’invité, qu’inconnu et que second utilisateur. C’est ce qui a révélé chacun des cas ci-dessus ; lire les règles ne l’a pas fait.

Pour un premier passage, le [Vérificateur de règles Firebase](https://madsalt.tech/fr/tools/firebase-rules-checker/) gratuit lit des règles collées sans toucher à votre projet. Sur les jeux de règles de cet article, il a signalé le mode test, l’accès réservé aux utilisateurs connectés, le `match` large et le rôle écrit par l’utilisateur. Il ne lit que du texte : il ne peut pas vous dire à quoi ressemblent vos données ni si une règle a été déployée.

Votre `apiKey` dans la page n’est pas le problème en soi. La configuration web de Firebase est publique par conception, et le [scanner de secrets](https://madsalt.tech/fr/tools/secret-scanner/) gratuit la signale comme telle, car ce sont les règles qui protègent les données derrière. Les fichiers suivent encore d’autres règles, avec une fuite que les règles ne peuvent pas arrêter, que j’ai testée dans les règles Firebase Storage.

**Limites.** Firestore uniquement, dans l’émulateur local, via REST, sur des données inventées. Realtime Database utilise un autre langage de règles et n’a pas été testée. App Check, qui limite les applications autorisées à appeler votre backend, n’en fait pas partie ; il ne remplace pas les règles. La production peut différer de l’émulateur sur des détails que je n’ai pas vérifiés.

Vous ne savez pas ce que vos règles laissent passer ?

Je les lirai volontiers avec vous. Sans démarchage.

[Me contacter](https://madsalt.tech/fr/#contact)
