RTDB Security Rules
RTDB rules are JSON-embedded expressions where access cascades downward: a permissive parent grants every descendant, and a restrictive child cannot revoke it. Lock the root, then open the smallest useful paths.
Rule types
.read/.write— WHO may act at this path (and everything below it)..validate— WHAT the written data may look like, evaluated only after.writeallows; validations do not cascade.data— pre-write state (the actor's existing context);newData— post-write state. In multi-field writes each.validatesees the full mergednewData.
Steps
Read current rules. From
database.rules.jsonin the project, or the deployed ruleset read back through the Firebase Console orfirebase-tools. Complete when you can state the effective access at every path a client touches (walk each cascade from root).Identify paths and identities. List each path clients read or write and the identity that should reach it (anonymous, any signed-in user, owner via
auth.uid, role via claim). Complete when each path has an intended identity × operation table.Design access and validation together. Start from
{ "rules": { ".read": false, ".write": false } }and open exact paths. Guard identity withauth !== nullbeforeauth.uidcomparisons. Add a.validatefor every user-controlled write: type checks (newData.isString(),.isNumber()), bounds, required children (newData.hasChildren([...])), and transition checks comparingdatatonewData. Complete when every open path has both an access rule and a shape rule.Simulate before shipping. Run
rtdb_simulate_accessfor each path with four case families: the intended actor allowed, anonymous denied, cross-user denied, invalid shape denied. Complete when all four families pass per path.Deploy. Write the full
database.rules.json, since a deploy replaces the entire ruleset, and ship it withfirebase deploy --only databaseor the Console. Complete when the deployed rules, read back through the Console orfirebase-tools, match the file, andrtdb_simulate_accessagainst a local copy of the same file still passes every case from step 4.
Reference — pitfalls
- A
.read: truenear the root silently exposes every descendant; recheck cascades after any parent edit. .validatenever runs when.writedenies — and never rescues a.writethat is too broad.dataat a path being created is empty; existence checks belong ondata.exists().- Deleting a node is a write of
null:newData.exists()in.validateblocks deletion — decide intentionally.