TL;DR
Firebase security depends on Firestore Security Rules (or Realtime Database Rules). Without proper rules, anyone can read and write your data. The Firebase config object is safe to expose, but your security rules are what actually protect your data. Write rules that verify user identity with request.auth, validate data structure, and test thoroughly before deploying.
Firebase Security Model
Firebase lets your frontend talk directly to the database. Security is enforced through rules that run on Firebase's servers, not your code. Understanding this is essential:
- Config is public: Your Firebase config (apiKey, etc.) can be in your frontend code
- Rules are your security: Security rules determine what users can access
- Client-side code is untrusted: Never rely on frontend code for security
- Authentication provides identity: Rules use auth state to make decisions
Key concept: The Firebase API key is not a secret. It's like a project identifier. Your Firestore Security Rules are what actually protect your data.
Firestore Security Rules Basics
The Danger of Test Mode
When you create a Firebase project, you might start in "test mode" which allows all access:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// This allows ANYONE to read and write EVERYTHING
allow read, write: if true;
}
}
}
Never deploy with test mode rules. This is equivalent to having no security at all. Anyone who finds your Firebase project can read, modify, or delete all your data.
Basic Secure Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Deny all by default
match /{document=**} {
allow read, write: if false;
}
// Users can only access their own document
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// Public posts are readable, authors can write
match /posts/{postId} {
allow read: if resource.data.published == true;
allow create: if request.auth != null;
allow update, delete: if request.auth != null
&& request.auth.uid == resource.data.authorId;
}
}
}
Common Security Rules Patterns
Pattern 1: User-Owned Data
match /profiles/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// For subcollections owned by users
match /users/{userId}/notes/{noteId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
Pattern 2: Data Validation
match /posts/{postId} {
allow create: if request.auth != null
// Validate required fields exist
&& request.resource.data.keys().hasAll(['title', 'content', 'authorId'])
// Validate title length
&& request.resource.data.title.size() <= 200
// Validate authorId matches the user
&& request.resource.data.authorId == request.auth.uid
// Validate content is a string
&& request.resource.data.content is string;
}
Pattern 3: Role-Based Access
// Helper function to check admin status
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
match /adminData/{docId} {
allow read, write: if request.auth != null && isAdmin();
}
match /users/{userId} {
// Users can read their own data
allow read: if request.auth != null && request.auth.uid == userId;
// Only admins can write to any user
allow write: if request.auth != null && isAdmin();
}
Pattern 4: Time-Based Rules
match /comments/{commentId} {
allow create: if request.auth != null
// Check if user has commented recently (basic rate limit)
&& (!exists(/databases/$(database)/documents/userMeta/$(request.auth.uid))
|| get(/databases/$(database)/documents/userMeta/$(request.auth.uid)).data.lastComment
< request.time - duration.value(30, 's'));
}
Firebase Authentication Security
Secure Sign-Up Configuration
Configure authentication properly in Firebase Console:
Authentication Security Checklist
Enable only the auth providers you actually use
Set password requirements (length, complexity)
Enable email verification for email/password auth
Configure authorized domains (don't allow all)
Set up proper OAuth redirect URLs
Review and limit sign-up rate if needed
Custom Claims for Authorization
// In Cloud Functions or Admin SDK
const admin = require('firebase-admin');
// Set admin claim for a user
await admin.auth().setCustomUserClaims(userId, { admin: true });
// The claim will be available in security rules:
// request.auth.token.admin == true
match /adminOnly/{docId} {
allow read, write: if request.auth != null
&& request.auth.token.admin == true;
}
Cloud Storage Security Rules
Firebase Storage also needs security rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// User profile images
match /users/{userId}/profile.jpg {
allow read: if true; // Public profile images
allow write: if request.auth != null
&& request.auth.uid == userId
&& request.resource.size < 5 * 1024 * 1024 // 5MB max
&& request.resource.contentType.matches('image/.*');
}
// Private user files
match /users/{userId}/private/{fileName} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// Deny everything else
match /{allPaths=**} {
allow read, write: if false;
}
}
}
Testing Security Rules
Firebase provides tools to test your rules before deploying:
Firebase Emulator
// Install Firebase CLI
npm install -g firebase-tools
// Start emulators
firebase emulators:start
// Run tests
firebase emulators:exec --only firestore "npm test"
Rules Playground
In Firebase Console, use the Rules Playground to simulate requests:
- Go to Firestore > Rules > Edit rules
- Click "Rules Playground"
- Simulate different auth states and operations
- Verify rules allow/deny as expected
Common Firebase Security Mistakes
| Mistake | Risk | Fix |
|---|---|---|
| Test mode in production | Anyone can read/write all data | Write proper security rules |
| Trusting client data | Users can send any data | Validate all fields in rules |
| Not checking auth | Unauthenticated access | Always check request.auth |
| Overly broad rules | Access to unintended data | Use specific path matches |
| Storing secrets in Firestore | Exposure via rules bypass | Use Cloud Functions for secrets |
Is my Firebase API key secure?
The Firebase API key (and other config values) are designed to be public. They identify your project but don't grant access. Your security comes from Firestore Security Rules, not from hiding the config. Don't worry if someone sees your firebase config object.
Can I hide my Firebase config?
You can't truly hide it because it needs to be in your frontend code for Firebase to work. Instead of trying to hide it, focus on writing proper security rules. The config is not a security credential.
What's the difference between Firestore rules and Realtime Database rules?
They use different syntax. Firestore rules use a match/allow syntax and can reference other documents. Realtime Database rules use JSON with .read/.write/.validate. Firestore rules are generally more flexible and powerful.
Should I validate data in my app or in security rules?
Both. Validate in your app for good UX (show errors immediately). Validate in security rules for actual security (server-side enforcement). Never skip rule validation because client-side code can be bypassed.
Check Your Firebase Security
Scan your Firebase project for insecure rules and misconfigurations.
Start Free Scan