TL;DR
The #1 Firebase security best practice is replacing test mode rules with proper security rules before production. These 7 practices take about 50 minutes to implement and eliminate 89% of Firebase security vulnerabilities. Focus on: writing granular security rules, validating data in rules, using custom claims for roles, and keeping admin SDK credentials server-side only.
"Security rules are your last line of defense. They run on Firebase servers, so users cannot bypass them no matter what they do in your app."
Never Deploy with Test Mode Rules
Firebase creates test mode rules during development that allow all reads and writes. These must be replaced before production.
Critical: Test mode rules (allow read, write: if true) give anyone full access to your database. Firebase will send warnings, but many developers ignore them. Replace these immediately.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// THIS IS DANGEROUS - REMOVE BEFORE LAUNCH
allow read, write: if true;
}
}
}
Best Practice 1: Write Proper Security Rules 10 min
Firebase security rules control access to Firestore and Realtime Database. Here are patterns for common scenarios:
User-Owned Documents
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// User profiles
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null && request.auth.uid == userId;
allow update: if request.auth != null && request.auth.uid == userId;
allow delete: 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;
}
}
}
Public Read, Owner Write
match /posts/{postId} {
// Anyone can read published posts
allow read: if resource.data.published == true;
// Authors can read their own drafts
allow read: if request.auth != null
&& resource.data.authorId == request.auth.uid;
// Only authenticated users can create posts they own
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
// Only authors can update their posts
allow update: if request.auth != null
&& resource.data.authorId == request.auth.uid;
// Only authors can delete their posts
allow delete: if request.auth != null
&& resource.data.authorId == request.auth.uid;
}
Best Practice 2: Validate Data in Rules 5 min
Security rules should validate incoming data, not just check authentication:
match /posts/{postId} {
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid
// Validate required fields exist
&& request.resource.data.keys().hasAll(['title', 'content', 'authorId', 'createdAt'])
// Validate data types
&& request.resource.data.title is string
&& request.resource.data.content is string
// Validate field lengths
&& request.resource.data.title.size() >= 3
&& request.resource.data.title.size() <= 200
&& request.resource.data.content.size() <= 50000
// Validate timestamp
&& request.resource.data.createdAt == request.time;
allow update: if request.auth != null
&& resource.data.authorId == request.auth.uid
// Prevent changing author
&& request.resource.data.authorId == resource.data.authorId
// Validate updated fields
&& request.resource.data.title.size() >= 3
&& request.resource.data.title.size() <= 200;
}
Best Practice 3: Use Custom Claims for Roles 10 min
For admin or role-based access, use Firebase custom claims instead of database lookups:
const admin = require('firebase-admin');
// Set admin claim for a user
exports.setAdminRole = functions.https.onCall(async (data, context) => {
// Verify caller is already an admin
if (!context.auth.token.admin) {
throw new functions.https.HttpsError(
'permission-denied',
'Only admins can create new admins'
);
}
const { userId } = data;
await admin.auth().setCustomUserClaims(userId, { admin: true });
return { success: true };
});
match /admin/{document=**} {
// Only users with admin claim can access
allow read, write: if request.auth != null
&& request.auth.token.admin == true;
}
match /posts/{postId} {
// Admins can delete any post
allow delete: if request.auth != null
&& (resource.data.authorId == request.auth.uid
|| request.auth.token.admin == true);
}
Best Practice 4: Secure Cloud Functions 10 min
Cloud Functions run with admin privileges. Secure them properly:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// Always verify authentication for callable functions
exports.secureFunction = functions.https.onCall(async (data, context) => {
// Check authentication
if (!context.auth) {
throw new functions.https.HttpsError(
'unauthenticated',
'Authentication required'
);
}
// Validate input data
const { itemId } = data;
if (!itemId || typeof itemId !== 'string') {
throw new functions.https.HttpsError(
'invalid-argument',
'Invalid item ID'
);
}
// Verify authorization (user owns the resource)
const item = await admin.firestore()
.collection('items')
.doc(itemId)
.get();
if (!item.exists || item.data().userId !== context.auth.uid) {
throw new functions.https.HttpsError(
'permission-denied',
'Access denied'
);
}
// Perform operation...
return { success: true };
});
Best Practice 5: Protect API Keys 3 min
Firebase uses different credentials for different purposes:
| Credential | Purpose | Exposure Safe? | Notes |
|---|---|---|---|
| Web API Key | Client SDK init | Yes | Security rules protect data |
| Project ID | Identify project | Yes | Public identifier |
| Service Account JSON | Admin SDK | No | Never expose, server only |
| FCM Server Key | Push notifications | No | Use FCM v1 API instead |
Never commit service account JSON files to git. These provide full admin access to your Firebase project. Use environment variables or secret managers in production.
Best Practice 6: Configure Authentication Properly 5 min
Secure your Firebase Authentication settings:
Firebase Auth Configuration Checklist:
- Enable email enumeration protection
- Set up authorized domains (remove localhost in production)
- Configure password policy (minimum length, complexity)
- Enable email verification for new accounts
- Set up appropriate OAuth redirect URLs
- Review and configure session duration
Best Practice 7: Monitor and Audit 5 min
Set up monitoring for security events:
- Rules Playground: Test rules in the Firebase console before deploying
- Firestore Audit Logs: Enable in Google Cloud Console
- Security Rules Monitor: View denied requests in Firebase console
- Cloud Functions Logs: Monitor for errors and suspicious activity
Common Firebase Security Mistakes
| Mistake | Impact | Prevention |
|---|---|---|
| Test mode rules in production | Full data exposure | Deploy proper rules before launch |
| Service account in client code | Complete project compromise | Server-side only, use secrets |
| No data validation in rules | Data corruption, injection | Validate all incoming data |
| Using database for roles | Role bypass possible | Use custom claims instead |
| Unprotected Cloud Functions | Unauthorized operations | Always check auth in functions |
Official Resources: For the latest information, see Firebase Security Rules Documentation, Firebase Authentication Guide, and Cloud Functions Security Best Practices.
Is it safe to expose Firebase config in my frontend code?
Yes, the Firebase web configuration (apiKey, projectId, etc.) is designed to be public. Your security comes from security rules, not from hiding these values. The apiKey only identifies your project and does not grant access.
How do I test security rules before deploying?
Use the Rules Playground in the Firebase console to simulate requests with different authentication states. You can also write unit tests for rules using the Firebase Emulator Suite with the @firebase/rules-unit-testing package.
Should I validate data in rules or in my app?
Both. App-side validation improves user experience with immediate feedback. Rules validation is essential for security because malicious users can bypass your app and call Firebase directly. Rules are your last line of defense.
How do I implement admin access in Firebase?
Use custom claims set via the Admin SDK in Cloud Functions. Check these claims in security rules with request.auth.token.admin. Never store admin status in a database document that users could potentially modify.
Verify Your Firebase Security
Scan your Firebase project for security rule issues and misconfigurations.
Start Free Scan