Il tuo database Firestore è lento? Le regole di sicurezza sono un incubo? O peggio, le hai lasciate di default e ti sei beccato una notifica di spesa elevata a fine mese? Se lavori con Firebase, sai che Firestore è potente, ma senza regole ben scritte diventa un pozzo di costi e vulnerabilità. Noi, di Meteora Web, lo vediamo spesso nei progetti che ci arrivano: regole troppo permissive (o assenti), query inefficienti che leggono migliaia di documenti inutili, o peggio, dati esposti a chiunque.
Partiamo da una premessa tecnica che molti sottovalutano: Firestore è un database NoSQL orientato ai documenti e accessibile direttamente dal client. Non c'è un backend intermedio a filtrare i dati. Le regole di sicurezza sono l'unico baluardo tra i tuoi dati e un utente malevolo. In questa guida ti spieghiamo come impostarle correttamente e come scrivere query che siano efficienti e coperte dalle regole, evitando errori e sprechi.
Perché le regole di sicurezza non sono un optional
Immagina una banca con la porta d'ingresso spalancata. Chiunque entrerebbe e prenderebbe ciò che vuole. Ecco, un database Firestore senza regole di sicurezza è esattamente così. Molti sviluppatori, nella fretta di far funzionare un prototipo, impostano regole permissive e poi se ne dimenticano. Risultato: chiunque può leggere e scrivere dati, e tu paghi per ogni operazione.
Errore comune: allow read, write: if true;. Questo è lecito solo in fase di sviluppo locale. Mai in produzione. Noi abbiamo ereditato progetti con 100.000 letture al giorno da bot non autorizzati. Il conto arrivava a centinaia di euro al mese.
Le regole di sicurezza di Firestore si basano su tre concetti:
- match: definisce il percorso (collection o documento) a cui si applicano le regole.
- allow: specifica l'operazione consentita (
read,write,create,update,delete). - condizioni: espressioni booleane che verificano autenticazione, campi del documento, stato della richiesta.
Struttura base
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}Questa regola blocca tutto. Poi apriamo selettivamente secondo le nostre esigenze.
Regole basate sull'autenticazione
Il caso più comune: solo utenti autenticati possono leggere i propri dati. Usiamo request.auth.uid per identificare l'utente.
match /users/{userId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId;
}Qui ogni utente può leggere e scrivere solo il proprio documento nella collection users. Attenzione: questo presuppone che il nome del documento sia l'UID dell'utente. È una best practice.
Regole basate sui campi del documento
Spesso serve verificare non solo chi accede, ma anche cosa sta leggendo o scrivendo. Esempio: un post in un blog ha il campo published: true. Solo i post pubblicati possono essere letti da tutti, gli altri solo dall'autore.
match /posts/{postId} {
allow read: if resource.data.published == true || request.auth.uid == resource.data.authorId;
allow create: if request.auth.uid != null;
allow update, delete: if request.auth.uid == resource.data.authorId;
}Nota: resource.data si riferisce al documento attuale nel database, request.resource.data (solo in scrittura) al documento proposto.
Query efficienti e regole di sicurezza correlate
Uno degli errori più subdoli: scrivere una regola che sembra corretta, ma quando il client esegue una query, Firestore la rifiuta perché la regola non riesce a garantire che la query restituisca solo documenti consentiti. Firestore valuta la condizione sulla base di indici e filtri. Se la query non è coperta dalla regola, ottieni un errore di permesso negato, anche se il documento esiste.
Esempio: supponiamo di avere una collection messages in una chat, con campo roomId e userId. Vogliamo che un utente veda solo i messaggi delle stanze a cui partecipa.
match /messages/{messageId} {
allow read: if request.auth.uid != null &&
resource.data.visibility == 'public' ||
resource.data.participants.hasAny([request.auth.uid]);
}Se il client esegue db.collection('messages').where('roomId', '==', 'room1'), la regola fallisce perché non può sapere se i messaggi di room1 hanno partecipanti autorizzati. Firestore esamina l'indice e la condizione: la regola richiede di controllare participants su ogni documento, ma la query non filtra su quel campo. Quindi la regola viene rifiutata.
Soluzione: la query deve rispecchiare esattamente le condizioni della regola. Ovvero, filtrare per participants o cambiare struttura dati. Oppure usare getAfter per validare senza esporre tutti i documenti.
Usare getAfter per validare scritture dipendenti
Se devi verificare qualcosa in un altro documento (es. il saldo utente prima di un acquisto), puoi usare getAfter(). Esempio: in un e-commerce, prima di creare un ordine, controlliamo che l'utente abbia credito sufficiente.
match /orders/{orderId} {
allow create: if request.auth.uid == request.resource.data.userId &&
getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.balance >= request.resource.data.amount;
}getAfter legge lo stato del database dopo la scrittura, utile per evitare letture extra nel client.
Esempi operativi con codice
Chat privata
Collection rooms con campo members (array di uid).
match /rooms/{roomId} {
allow read: if request.auth.uid != null && resource.data.members.hasAny([request.auth.uid]);
allow create, update: if request.auth.uid != null && request.resource.data.members.hasAny([request.auth.uid]);
}Il client esegue: db.collection('rooms').where('members', 'array-contains', auth.uid). Questa query è coperta dalla regola perché il filtro è sullo stesso campo usato nella condizione (members).
E-commerce – solo admin modifica prezzi
Vogliamo che tutti gli utenti autenticati possano leggere i prodotti, ma solo gli admin possano modificarli. Supponiamo di avere una collection admins con documenti di uid.
match /products/{productId} {
allow read: if true; // pubblico
allow write: if request.auth.uid != null && exists(/databases/$(database)/documents/admins/$(request.auth.uid));
}In questo caso exists verifica l'esistenza del documento admin. Attenzione: exists usa una lettura extra, quindi può aumentare i costi. Meglio usare custom claims: request.auth.token.admin == true se impostato tramite Firebase Admin SDK.
allow write: if request.auth.token.admin == true;I custom claims sono più performanti perché non richiedono letture.
Strumenti per testare e debuggare
Non fidarti mai di regole scritte a occhio. Usa il Simulator nella console Firebase (sotto Firestore > Regole). Puoi simulare una richiesta con un utente autenticato o anonimo e vedere se passa o fallisce. Ancora meglio: usa gli Emulator Suite in locale. Ti permette di testare regole, query e comportamenti senza consumare risorse reali.
firebase emulators:start --only firestore,authPoi collega il client all'emulatore. Noi lo usiamo sempre in fase di sviluppo: velocizza il ciclo di debug e non costa nulla.
Checklist per regole sicure
- Blocca tutto di default:
allow read, write: if false;per tutte le collection. - Apri selettivamente: match per ogni collection, con condizioni basate su autenticazione e dati.
- Usa custom claims per ruoli (admin, editor) invece di letture extra.
- Verifica che le query del client corrispondano alle condizioni delle regole (stessi campi di filtro).
- Testa con il Simulator almeno i flussi principali (login, lettura dati propri, scrittura, accesso negato).
- Non esporre mai
allow read, write: if true;in produzione.
In sintesi — cosa fare adesso
- Vai nella console Firebase, sezione Firestore > Regole, e blocca tutte le regole esistenti con
if false. - Identifica le collection e i casi d'uso (utente, admin, pubblico).
- Scrivi regole specifiche per ogni percorso, testandole con il Simulator.
- Per query complesse, assicurati che il filtro lato client sia uguale alla condizione nella regola.
- Se usi ruoli, passa a custom claims: sono più veloci e economici.
- Attiva gli emulator per lo sviluppo locale.
Hai già le regole in produzione? Controlla subito che non ci siano allow read, write: if true; o regole troppo larghe. Un'ora di verifica oggi può evitarti una brutta sorpresa sul prossimo report dei costi.
Sponsored Protocol