---
title: "Firebase security rules: 11 regler, jeg angreb"
description: "Firebase security rules testet i emulatoren. Hvad hver regel lod en gæst eller anden bruger gøre, hvad der stod åbent, og hvilken regel der lukkede det."
url: "https://madsalt.tech/da/blog/firebase-security-rules-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/da/about/"
language: "da"
published: 2026-11-03
---

# Firebase security rules: 11 regler, jeg angreb

TL;DR

Jeg sendte de samme anmodninger mod 11 Firestore-regelsæt som gæst, anonym bruger og tre loggede brugere. `request.auth != null` lod en fremmed læse, ændre og slette alles ordrer. Et ejertjek alene på `resource` lod en bruger give en ordre videre til en anden. Ét bredt `{document=**}`-match åbnede en samling, der havde en korrekt ejerregel. Og en rolle gemt i et dokument, brugeren selv kan skrive til, gjorde dem til admin med én anmodning. Hver af dem har en rettelse nedenfor, og den rettede regel holdt mod de samme anmodninger.

En Firebase-app har ingen server af din egen mellem browseren og databasen. Projektets konfiguration ligger i siden, og [security rules](https://firebase.google.com/docs/rules/insecure-rules) er det eneste, der står mellem enhver, der har den, og dine data. Så jeg ville have eksempler på regler med angrebet ved siden af, ikke en liste over regler, der bare ser rigtige ud.

**Sådan testede jeg.** Firebase Local Emulator Suite fra Firebase CLI 15.34.2, den 3. november 2026. Jeg indlæste hvert regelsæt, nulstillede data og kaldte Firestore REST API med rigtige tokens fra Auth-emulatoren: intet token, en anonym bruger, brugerne A og B og en fremmed, der kun havde oprettet sig. Data var opfundne: ordrerne `a1` og `a2` ejes af A, `b1` ejes af B.

## Hvilke Firebase-regler lækker?

| Regel                                     | Anmodning                              | Resultat      |
| ----------------------------------------- | -------------------------------------- | ------------- |
| `if true` (testtilstand)                  | Gæst lister ordrer                     | `200`, alle 3 |
| `if true` (testtilstand)                  | Gæst sletter Bs ordre                  | `200`         |
| `request.auth != null`                    | Anonym bruger lister ordrer            | `200`, alle 3 |
| `request.auth != null`                    | Fremmed sletter As ordre               | `200`         |
| Ejertjek kun på `resource`                | A opretter en ordre ejet af B          | `200`         |
| Ejertjek kun på `resource`                | A ændrer `ownerId` på egen ordre       | `200`         |
| Ejerregel plus læsning på `{document=**}` | A læser Bs ordre                       | `200`         |
| Bruger kan skrive eget `users`-dokument   | A sætter `role: admin`, læser Bs ordre | `200`         |

## Hvad åbner testtilstand egentlig?

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

Alt, for alle. En gæst uden token listede alle 3 ordrer, slettede én og oprettede en ny. Intet af det kræver en konto eller en lækket hemmelighed, kun projekt-id'et.

Den daterede version, som testtilstand ofte bruger, fejler på en anden måde. Med `if request.time < timestamp.date(2026, 1, 1)` og den dato i fortiden fik gæsten `403`, og det samme gjorde bruger A, der læste sin egen ordre. Lækagen er forbi, og det er appen også.

## Er `request.auth != null` nok?

Nej. Den siger kun, at der er en eller anden konto bag anmodningen.

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

Gæsten fik `403`, og derfor føles reglen sikker i en hurtig test. Så listede en fremmed, der kun havde oprettet sig, alle 3 ordrer, ændrede totalen på Bs ordre og slettede As ordre, hver gang med `200`. En anonym bruger listede også alle 3. Hvis tilmelding er åben, betyder reglen "alle, der gider registrere sig". Firebase-dokumentationen nævner den også som et [almindeligt usikkert mønster](https://firebase.google.com/docs/rules/insecure-rules).

## Hvorfor skal et ejertjek bruge både `resource` og `request.resource`?

`resource` er dokumentet, som det er gemt. `request.resource` er dokumentet, som det ville se ud efter skrivningen. En regel, der kun ser på det første, tjekker, hvem der ejer rækken nu, og siger intet om, hvad den bliver lavet om til.

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

Læsning så rigtig ud: A fik `200` på sin egen ordre og `403` på Bs. Skrivning gjorde ikke. A oprettede en ny ordre med `ownerId` sat til B og fik `200`. A ændrede derefter `ownerId` på sin egen ordre til B, også `200`, og B kunne læse den fra da af. I en app, hvor ordrer betyder fakturaer eller saldi, er det en bruger, der skriver ind på en andens konto.

Denne version afviste det hele:

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

| Anmodning fra A             | Resultat |
| --------------------------- | -------- |
| Læs egen ordre              | `200`    |
| Læs, opdater, slet Bs ordre | `403`    |
| Opret en ordre ejet af B    | `403`    |
| Ændr `ownerId` til B        | `403`    |

Den lod stadig A ændre `total` på sin egen ordre, med `200`. Hvis klienten ikke må sætte det felt, så skriv, hvilke felter den må ændre:

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

Med den gav en ændring af `total` `403`, og en ændring af `note` gav `200`.

## Hvorfor fejlede min forespørgsel med en korrekt regel?

Fordi [regler ikke er filtre](https://firebase.google.com/docs/firestore/security/rules-query). Under ejerreglen ovenfor fik A `403`, da A bad om hele samlingen, selvom A ejer 2 af de 3 ordrer. Firestore vurderer forespørgslen ud fra, hvad den kunne returnere, ikke ud fra, hvad der er.

| Anmodning fra A          | Resultat      |
| ------------------------ | ------------- |
| List alle ordrer         | `403`         |
| Forespørg `ownerId == A` | `200`, 2 dok. |
| Forespørg `ownerId == B` | `403`         |

Så når en liste går i stykker, efter du har strammet reglerne, så ret forespørgslen. At løsne reglen for at få listen til at virke er sådan, de brede regler ovenfor bliver skrevet.

## Kan et bredt match ophæve en korrekt regel?

Ja, og det er den, jeg ville lede efter først i et projekt med en lang regelfil. Firestore [tillader en anmodning, hvis en matchende regel tillader den](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;
}
```

Den første blok ligner en god ejerregel. Med den anden blok til stede læste A Bs ordre med `200`, en fremmed listede alle 3 ordrer, og den samme fremmede læste As dokument i `users`. Intet i den første blok advarer dig. Slet det brede match; der er ingen måde, en senere regel kan tage adgang tilbage.

## Hvordan gør en bruger sig selv til admin?

Mønsteret dukker op, hver gang roller ligger i et dokument:

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

"Brugere kan redigere deres egen profil" og "admins kan læse alle ordrer" er hver for sig rimelige. Sammen er de en rettighedseskalering.

![Admin med én anmodning. Bruger A, hvis eget users-dokument kan skrives. Før: Bruger A → læser Bs ordre → 403; Skriv egen rolle: Bruger A → role: admin → 200; Efter: Bruger A → læser Bs ordre → 200.](https://madsalt.tech/charts/da/firebase-security-rules-examples.svg)

*Figur 1. Min test i Firebase-emulatoren (CLI 15.34.2), 3. november 2026.*

Løsningen er at stoppe klienten i at skrive det felt. Med `request.resource.data.role == resource.data.role` tilføjet til skrivereglen gav den samme anmodning `403`, og A forblev uden for Bs ordre. Custom claims sat fra en server er den anden vej, da en klient ikke kan ændre dem.

## To ting, der viste sig at være i orden

Regler på en forælder lækker ikke ned i undersamlinger. Med `allow read` på `/orders/{id}` for enhver loggeret bruger læste A ordren, og `/orders/b1/notes/n1` under den gav `403`. Det er den [dokumenterede opførsel](https://firebase.google.com/docs/firestore/security/rules-structure): regler gælder kun på den matchede sti.

En gæst slipper ikke igennem en sammenligning med et manglende token. `resource.data.ownerId == request.auth.uid` gav `403` til gæsten, også uden et separat `request.auth != null`. Jeg skriver alligevel tjekket ud, fordi reglen så siger, hvad den mener.

## Hvordan tjekker du dine egne regler?

Kør dine anmodninger i emulatoren som gæst, som fremmed og som en anden bruger. Det afslørede alle tilfældene ovenfor; at læse reglerne gjorde ikke.

Til en første gennemgang læser det gratis [Firebase regeltjek](https://madsalt.tech/da/tools/firebase-rules-checker/) indsatte regler uden at røre dit projekt. På regelsættene her flagede det testtilstand, adgang kun med login, det brede match og den selvskrevne rolle. Det læser kun tekst, så det kan ikke fortælle dig, hvordan dine data ser ud, eller om en regel er udrullet.

Din `apiKey` i siden er ikke problemet i sig selv. Firebase web-konfiguration er offentlig med vilje, og den gratis [Secret scanner](https://madsalt.tech/da/tools/secret-scanner/) markerer den sådan, fordi det er reglerne, der beskytter dataene bag den. Filer følger igen andre regler, med en lækage som regler ikke kan stoppe, og den testede jeg i Firebase Storage rules.

**Begrænsninger.** Kun Firestore, i den lokale emulator, over REST, på opfundne data. Realtime Database bruger et andet regelsprog og blev ikke testet. App Check, som begrænser, hvilke apps der må kalde din backend, er ikke med; den erstatter ikke regler. Produktion kan afvige fra emulatoren i detaljer, jeg ikke har tjekket.

Usikker på, hvad dine regler lader slippe igennem?

Jeg læser dem gerne sammen med dig. Ingen salgstale.

[Kontakt mig](https://madsalt.tech/da/#contact)
