Firebase security rules after test mode: why auth != null isn't enough
Firebase test mode lets anyone read and overwrite your database, and it is only meant for the first days of a project. When it ends, the usual quick fixes are if true, which reopens everything, or if request.auth != null, which lets any signed-in user read and edit every other user's data. The real replacement is a rule that compares the signed-in user's ID with an owner field on each document, plus a test that proves a second account can't get in.
What test mode is, and what happens when it ends
When you create a Cloud Firestore database, Firebase asks you to choose between two starting configurations. Its documentation describes test mode as good for getting started with the client libraries, but one that allows anyone to read and overwrite your data. Production mode does the opposite: it denies all reads and writes from mobile and web clients.
Test mode is temporary by design. For the Realtime Database, the docs spell out the end of it: if you keep the default world-readable and world-writable rules past a trial period, Firebase emails you, and then your rules deny all requests.
That last step is when most apps break, often weeks after launch. The app stops loading data, someone opens the Rules tab under pressure, and one of two lines goes in.
The two quick fixes that don't protect anything
The first one:
allow read, write: if true;
Firebase's page on fixing insecure rules lists this as open access, the pattern that allows anyone to overwrite your entire database. It is test mode without the expiry date.
The second one looks responsible:
allow read, write: if request.auth != null;
It checks that a request comes from someone who is signed in. It says nothing about which documents that person should see. The same Firebase page asks you to confirm that you really want any logged-in user to have access to the data before relying on this check alone.
Why "signed in" is the wrong question
Your Firebase configuration, API key included, is part of your client app. Firebase's documentation states that this is fine: keys restricted to Firebase services don't need to be treated as secrets, because data is protected by Security Rules and App Check, not by hiding the key.
The consequence is easy to miss. If email and password sign-up or anonymous sign-in is enabled, anyone can take your public config, create an account from a browser console or a short script, and become an authenticated user. From that moment, request.auth != null is true for them on every document in every collection your rules cover.
So the question a rule should answer isn't "is someone logged in?" but "does this document belong to the person asking?".
The same page flags a related trap: a rule that trusts an email domain, for example only accepting addresses ending in @example.com, is unsafe unless it also requires a verified email. Without that check, anyone can sign in with an arbitrary address on your domain.
An owner-only rule for Firestore
Take a notes collection where each document stores the creator's user ID in owner_uid. This set of rules, adapted from Firebase's content-owner pattern, covers each operation separately:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /notes/{noteId} {
allow read, delete: if request.auth != null
&& request.auth.uid == resource.data.owner_uid;
allow create: if request.auth != null
&& request.auth.uid == request.resource.data.owner_uid;
allow update: if request.auth != null
&& request.auth.uid == resource.data.owner_uid
&& request.resource.data.owner_uid == resource.data.owner_uid;
}
}
}
A few details carry the weight here:
resource.datais the document as it exists now;request.resource.datais the document as it would be after the write.- On
create, the user can only create a note that names themselves as owner. - On
update, the final condition stops a user from editing their note into someone else's account, or claiming someone else's note by changing the field.
Anything not matched by a rule is denied, so collections you haven't written rules for stay closed.
Rules are not filters: fix your queries too
After switching to owner-only rules, a common surprise is that list screens stop working. Firestore evaluates a query against everything it could possibly return, not against the documents that happen to exist. In the documentation's words, security rules are not filters and queries are all or nothing.
A query for the whole collection fails, even if every note in it belongs to the current user:
db.collection('notes').get(); // denied
The query has to carry the same constraint as the rule:
db.collection('notes').where('owner_uid', '==', user.uid).get(); // allowed
For shared data, such as notes visible to every member of a team, store the member IDs in an array and use the same idea in both places: request.auth.uid in resource.data.members in the rule, and where('members', 'array-contains', user.uid) in the query. Who can add or remove members then needs its own update rule.
Test the rules before you deploy them
In the console. The Rules Playground in the Firebase console simulates a request against your draft rules. Choose an authenticated request, enter a UID that is not the owner, and try to read one of your test documents. It should be denied. Then repeat with the owner's UID.
In code. The Local Emulator Suite with @firebase/rules-unit-testing turns the same checks into tests that run on every change:
import { readFileSync } from 'node:fs';
import { initializeTestEnvironment, assertFails, assertSucceeds } from '@firebase/rules-unit-testing';
const env = await initializeTestEnvironment({
projectId: 'demo-notes',
firestore: { rules: readFileSync('firestore.rules', 'utf8') },
});
await env.withSecurityRulesDisabled(async (ctx) => {
await ctx.firestore().doc('notes/n1').set({ owner_uid: 'alice', text: 'draft' });
});
const alice = env.authenticatedContext('alice').firestore();
const mallory = env.authenticatedContext('mallory').firestore();
await assertSucceeds(alice.doc('notes/n1').get());
await assertFails(mallory.doc('notes/n1').get());
await assertFails(mallory.collection('notes').add({ owner_uid: 'alice', text: 'x' }));
await assertFails(alice.doc('notes/n1').update({ owner_uid: 'mallory' }));
await env.cleanup();
Run it against the emulator with:
firebase emulators:exec --only firestore "npm test"
Firebase recommends a project ID starting with demo- for tests, so nothing can touch a real project.
If your app also has a web front end built on another backend, the same two-account idea applies there, as in this Supabase row level security check.
What rules don't cover
Security Rules apply to requests from the mobile and web client SDKs. Firebase's documentation notes that even in production mode, your authenticated application servers can still access the database, and that the server client libraries bypass all Cloud Firestore Security Rules. Any Cloud Function or backend route that reads data on a user's behalf needs its own ownership check in code.
FAQ
What happens when Firebase test mode expires?
Firebase's documentation describes it for the Realtime Database: you get an email, then the rules deny all requests, so the app stops reading and writing data. Replace them with rules scoped to each user rather than reopening access.
Is "allow read, write: if request.auth != null" secure?
Only if every signed-in user is meant to see and edit all of the data. Since anyone can create an account when public sign-up is enabled, this rule usually exposes every user's documents to every other user.
Is it safe to have my Firebase API key in the client?
Yes, for keys restricted to Firebase services. Firebase's documentation says data is protected by Security Rules and App Check, not by keeping that key secret. Keys for other APIs, such as the Gemini Developer API, must never ship in client code.
Sources
- Firebase, Create and manage Cloud Firestore databases (test and production mode)
- Firebase, Realtime Database: get started (trial period)
- Firebase, Fix insecure rules
- Firebase, Learn about using and managing API keys
- Firebase, Securely query data (rules are not filters)
- Firebase, Rules Playground
- Firebase, Build unit tests