"Firebase-beveiliging" zijn twee dingen die je beter uit elkaar houdt. Het eerste is het model van Firebase: de browser praat rechtstreeks met de database en de opslag, dus er is geen server die filtert, en de bescherming zit in de regels, in App Check en in de projectconfiguratie. Het tweede is wat Lovable, Bolt of Cursor met Firebase doen bij het genereren van code, en daar komen bijna alle gaten vandaan. Deze gids legt het model uit, loopt door wat de AI verkeerd doet, geeft je een firebase.json en basisregels om te plakken, een reviewprompt en de controle van buitenaf. De tien meest voorkomende regelfouten, met de foute en de goede regel, staan in de meest voorkomende beveiligingsfouten in Firebase.
Deel 1: hoe beveiliging werkt in Firebase
De client praat met de diensten, niet met jouw server
In een typische Firebase-app is er geen eigen backend: de browser-SDK leest en schrijft rechtstreeks in Firestore, Realtime Database en Storage. Dat betekent dat het configuratieobject met apiKey en projectId bewust publiek is (het identificeert het project, het autoriseert niets) en dat de enige barrière tussen een bezoeker en je data de beveiligingsregels zijn. Elk product heeft zijn eigen regels: Firestore, Realtime Database en Storage worden apart geconfigureerd, en een app heeft meestal open staan wat niemand heeft bekeken.
Regels beantwoorden "wie mag wat"
Een regel ontvangt het verzoek met de geauthenticeerde gebruiker (request.auth), het document dat gelezen of geschreven wordt en de nieuwe data (request.resource.data). Daarmee beslist hij. Het cruciale onderscheid is tussen geauthenticeerd (er is een sessie) en geautoriseerd (die sessie mag dat document zien): request.auth != null controleert alleen het eerste, en registreren is gratis. Regels valideren ook wat er geschreven wordt, zodat een gebruiker zichzelf geen isAdmin: true in zijn eigen profiel geeft.
Wat naast de regels beschermt
- App Check. Eist dat verzoeken uit je echte app komen (via reCAPTCHA of Play Integrity) en niet uit een script met je
projectId. Het remt misbruik en onverwachte rekeningen door massale leesbewerkingen. - Beperking van de API-sleutel. Beperk in de Google Cloud-console de sleutel tot de domeinen van je site en de API's die je gebruikt. Het vervangt de regels niet, maar sluit een deur.
- Auth. Geautoriseerde domeinen voor inloggen, bescherming tegen e-mailenumeratie, e-mailverificatie, alleen de inlogmethodes die je echt gebruikt, meervoudige authenticatie als je gevoelige data verwerkt.
- Cloud Functions als server. Alles wat een geheime sleutel nodig heeft (betalingen, e-mail, AI) gaat in een functie, met het geheim in Secret Manager, en de client roept de functie aan. Let op: een HTTP-functie is publiek voor heel internet, tenzij je het token van de gebruiker verifieert of een callable functie met App Check gebruikt.
- Budget. Firebase rekent per leesbewerking en per aanroep. Budgetwaarschuwingen in Google Cloud en limieten in de regels (
request.query.limit) voorkomen dat een lus of een aanvaller je een rekening bezorgt.
Hosting: wat Firebase instelt en wat niet
Firebase Hosting geeft automatische TLS, een CDN en de redirect naar HTTPS. Het zet geen beveiligingsheaders: die voeg je toe in firebase.json. En het serveert alles in de publieke map, inclusief een per ongeluk gekopieerde .env.
Deel 2: wat de AI verkeerd doet met Firebase
- Regels in testmodus, en daarna
if true. De AI laat de regels staan die dertig dagen alles openzetten; bij het verlopen breekt de app en de snelle oplossing is ze voorgoed openzetten. request.auth != nullals enige voorwaarde. Elke geregistreerde gebruiker leest de data van alle anderen.- Ongevalideerde schrijfbewerkingen. Het profiel heeft
roleofcreditsen de update-regel sluit die velden niet uit. - Andere sleutels in de bundle. Naast de Firebase-configuratie verschijnen de geheime Stripe-sleutel, de OpenAI-sleutel of die van een e-maildienst. Details in geheimen die je app in de browser lekt.
- HTTP-functies die de gebruiker niet controleren. De functie die afrekent, e-mails verstuurt of een model aanroept, antwoordt iedereen die de URL kent.
- Storage met regels gekopieerd uit Firestore.
allow read, write: if request.auth != nullinstorage.rules: elke gebruiker leest de bestanden van anderen en uploadt wat hij wil. - Geen App Check, geen headers, geen waarschuwingen. Drie vinkjes die niemand zet omdat de app "al werkt".
Een blok om in je project te plakken
Beveiligingsheaders in firebase.json (de Content-Security-Policy gaat apart omdat die van je scripts afhangt):
{
"hosting": {
"public": "dist",
"headers": [
{
"source": "**",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
}
En een startpunt voor firestore.rules: standaard alles dicht, elke gebruiker zijn eigen document en beschermde privilegevelden.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null && request.auth.uid == userId
&& !request.resource.data.keys().hasAny(['role', 'isAdmin', 'plan', 'credits']);
allow update: if request.auth != null && request.auth.uid == userId
&& !request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['role', 'isAdmin', 'plan', 'credits']);
}
match /{document=**} {
allow read, write: if false;
}
}
}
Voeg daarna één expliciete regel per collectie toe; de laatste, met if false, garandeert dat elke nieuwe collectie gesloten geboren wordt.
Een reviewprompt vóór het publiceren
Beoordeel de beveiliging van deze Firebase-app en verander nog niets. Antwoord met een lijst:
1. Firestore-, Realtime Database- en Storage-regels die iedereen toegang geven, met een testmodusdatum of met request.auth != null als enige voorwaarde.
2. Schrijfregels die velden niet valideren, vooral role, isAdmin, plan of credits.
3. Andere sleutels dan de Firebase-configuratie in de browsercode, met bestand en regel.
4. HTTP Cloud Functions die het token van de gebruiker niet verifiëren, en functies die geheimen uit de code lezen in plaats van uit Secret Manager.
5. Of App Check is ingeschakeld en of de API-sleutel per domein is beperkt.
6. Beveiligingsheaders in firebase.json en bestanden in de publieke map die daar niet horen.
Stel voor elk punt de exacte regel of wijziging voor en wacht op mijn bevestiging.
Controleer het resultaat van buitenaf
Je projectId zit in de bundle, dus iedereen kan dit doen. De REST-API van Firestore zonder token:
curl -s "https://firestore.googleapis.com/v1/projects/JOUW_PROJECT/databases/(default)/documents/users" | head -c 400
curl -sI https://jouwdomein.com | grep -iE "strict-transport|x-frame|x-content-type"
Het juiste resultaat is een rechtenfout bij de eerste (geeft hij documenten terug, dan staat de collectie open) en de headers bij de tweede. Herhaal de eerste voor elke collectie en voor Storage. Scanaris voert deze controles en zo'n veertig andere uit in 30 seconden: het detecteert de Firebase-configuratie in je bundle, test de gebruikelijke collecties zonder authenticatie, zoekt andere sleutels in het JavaScript, controleert headers, CORS, cookies en blootgestelde bestanden, en geeft je voor elke bevinding een fix prompt om in Lovable, Bolt of Cursor te plakken. Gebruikt je app Supabase in plaats van Firebase, dan is de Supabase-gids het equivalent.
Veelgestelde vragen
Moet ik de Firebase-apiKey verbergen?
Nee. Hij is bewust publiek en identificeert het project. Wat de data beschermt, zijn de regels en App Check. Wat wel de moeite is: hem in Google Cloud beperken tot je domeinen.
Zijn Cloud Functions privé?
Een HTTP-functie is een publieke URL. Verifieer het token van de gebruiker in de functie of gebruik callable functies met App Check. Functies die op gebeurtenissen reageren (een aangemaakt document, een geüpload bestand) hebben geen URL en zijn wel intern.
Hoe test ik de regels zonder de app te breken?
Met de simulator in de console (tabblad Regels) om specifieke leesbewerkingen te testen, en met de lokale emulator voor geautomatiseerde tests. Wijzig de regels met de app open in een ander tabblad en controleer of wat moet werken nog steeds werkt.