How to Write Firebase Auth Rules
Protect your Firebase data with authentication-based security rules
TL;DR
TL;DR (20 minutes): Use request.auth != null to require authentication. Use request.auth.uid == resource.data.userId to ensure users only access their own data. Store user roles in custom claims or a separate collection, then check them in rules.
Prerequisites:
- Firebase project with Firestore or Realtime Database
- Firebase Authentication set up
- Basic understanding of Firebase security rules syntax
Why This Matters
Firebase security rules are your last line of defense. Even if your frontend code is "secure," anyone can call your Firebase APIs directly. Without proper auth rules, attackers can read all your data, modify other users' records, or delete everything.
The most common vulnerability in vibe-coded Firebase apps is leaving rules in "test mode" - wide open to the public. This guide shows you how to lock things down properly.
Step-by-Step Guide
Understand the request.auth object
When a user is authenticated, request.auth contains their info:
// request.auth structure
{
uid: "user123", // Unique user ID
token: {
email: "user@example.com",
email_verified: true,
name: "John Doe",
// Custom claims you've set
admin: true,
role: "premium"
}
}
If the user is not authenticated, request.auth is null.
Require authentication for all access
Start by locking down everything to authenticated users only:
// Firestore rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Default: deny all
match /{document=**} {
allow read, write: if false;
}
// Require authentication for specific collections
match /posts/{postId} {
allow read: if request.auth != null;
allow write: if request.auth != null;
}
}
}
Implement user-owned data rules
Ensure users can only access their own data:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// User profiles - users can only read/write their own
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// User's private data
match /users/{userId}/private/{document} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// Orders - users can only see their own orders
match /orders/{orderId} {
allow read: if request.auth != null
&& request.auth.uid == resource.data.userId;
allow create: if request.auth != null
&& request.auth.uid == request.resource.data.userId;
}
}
}
Add role-based access with custom claims
First, set custom claims in your backend:
// Node.js Admin SDK - set user as admin
const admin = require('firebase-admin');
async function setAdminRole(uid) {
await admin.auth().setCustomUserClaims(uid, { admin: true });
}
// Set premium role
async function setPremiumRole(uid) {
await admin.auth().setCustomUserClaims(uid, { role: 'premium' });
}
Then check claims in your rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Admin-only collection
match /admin/{document=**} {
allow read, write: if request.auth != null
&& request.auth.token.admin == true;
}
// Premium content
match /premium-content/{docId} {
allow read: if request.auth != null
&& request.auth.token.role == 'premium';
}
// Public content anyone authenticated can read
match /public/{docId} {
allow read: if request.auth != null;
allow write: if request.auth != null
&& request.auth.token.admin == true;
}
}
}
Alternative: Store roles in Firestore
If you can't use custom claims, store roles in a collection:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper function to check admin status
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
// Helper function to check any role
function hasRole(role) {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == role;
}
match /admin-settings/{docId} {
allow read, write: if request.auth != null && isAdmin();
}
match /premium/{docId} {
allow read: if request.auth != null && hasRole('premium');
}
}
}
Test your rules with the Emulator
Use the Firebase Emulator Suite to test rules locally:
# Install and start emulators
firebase emulators:start
# Run rules tests
firebase emulators:exec "npm test"
Write unit tests for your rules:
// rules.test.js
const { assertSucceeds, assertFails } = require('@firebase/rules-unit-testing');
describe('Firestore rules', () => {
it('allows users to read their own data', async () => {
const db = getFirestore({ uid: 'user123' });
await assertSucceeds(db.collection('users').doc('user123').get());
});
it('denies users from reading other users data', async () => {
const db = getFirestore({ uid: 'user123' });
await assertFails(db.collection('users').doc('other-user').get());
});
it('denies unauthenticated access', async () => {
const db = getFirestore(null); // No auth
await assertFails(db.collection('users').doc('anyone').get());
});
});
Common Mistakes to Avoid:
- Never leave rules in test mode (
allow read, write: if true) in production - Don't trust client-provided role data - always verify on the server or in rules
- Remember:
get()in rules counts as a read operation and affects billing - Custom claims are cached - users may need to sign out and back in to see changes
How to Verify It Worked
- Firebase Console: Go to Firestore → Rules → Run a simulation
- Try unauthorized access: Use an incognito browser to test unauthenticated requests
- Test cross-user access: Log in as User A and try to access User B's data
- Check the Rules Playground: Simulate different auth states in Firebase Console
Common Errors & Troubleshooting
Error: "Missing or insufficient permissions"
Your rules are denying the request. Check that the user is authenticated and has the required role/ownership.
Custom claims not working
Claims are cached in the ID token. Have the user sign out and back in, or force a token refresh with user.getIdToken(true).
Rules working locally but failing in production
Make sure you deployed your rules: firebase deploy --only firestore:rules
get() function returning null
The document doesn't exist. Add a null check: exists(/path/to/doc) && get(...).data.field
Custom claims vs. Firestore roles collection - which is better?
Custom claims are faster (no extra reads) and more secure (can't be modified by clients). Use them for simple roles. Use a Firestore collection when roles are complex, frequently changed, or need to store additional metadata.
How do I handle public data that anyone can read?
Create a separate collection for public data with allow read: if true but still restrict writes to authenticated users or admins.
Can I use Firebase Rules with anonymous authentication?
Yes, anonymous users still have a request.auth.uid. You can allow limited access to anonymous users while requiring full authentication for sensitive operations.
Related guides:Firebase Security Rules · Firebase Auth Setup · Supabase RLS Setup