Firebase Security Best Practices: Rules, Keys, and Headers
Firebase security failures follow a predictable pattern: a developer ships fast using permissive defaults, forgets to lock down Firestore rules before launch, and discovers the breach weeks later when user data surfaces on a leak forum. The good news is that every layer of the stack — rules, authentication, API keys, hosting headers — has a clear best practice. This guide covers all of them.
Check your site's security right now: Free ZeriFlow scan →
1. Firestore Security Rules: Deny by Default
The single most important Firebase security decision you'll make is your Firestore rules configuration. The default rules for a new project allow full read and write access to all authenticated users — that's every user who has created an account, not just admins.
Start with deny-all and build up:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Deny everything by default
match /{document=**} {
allow read, write: if false;
}
// Users can only read/write their own document
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Orders readable by owner, writable only by backend (service account)
match /orders/{orderId} {
allow read: if request.auth != null && request.auth.uid == resource.data.userId;
allow write: if false; // Server-side only via Admin SDK
}
}
}Critical rule patterns:
- Always validate
request.auth != nullbefore granting any access. - Use
request.auth.uid == resource.data.ownerIdto ensure users only touch their own data. - Validate incoming data shape with
request.resource.data.keys().hasOnly([...])to prevent field injection. - Use Firebase Rules Unit Testing (
@firebase/rules-unit-testing) to test your rules before deploying.
Never use allow read, write: if true in production. Run firebase deploy --only firestore:rules to push changes, and verify the published rules in the Firebase Console → Firestore → Rules.
2. Firebase Authentication Security
Firebase Auth is solid, but several configuration choices significantly affect your security posture.
Email/password authentication:
- Enable email enumeration protection in the Firebase Console (Authentication → Settings). Without it, the sign-in error messages reveal whether an email address is registered — useful to attackers.
- Set a strong password policy (minimum 12 characters, complexity requirements) via the Password Policy configuration.
- Enable email verification and gate access to protected resources behind
currentUser.emailVerified. A user who never verified their email may have used a fake address.
OAuth providers:
- Restrict which OAuth providers are enabled to only those you actively support. Disable test/development providers before launch.
- Validate the
idTokenon your backend for any server-side operations. Don't trust client-provided UIDs.
Session management:
- Firebase ID tokens expire after 1 hour. Refresh tokens are long-lived — treat them like passwords.
- Use
auth.currentUser.getIdTokenResult()to checkclaimsfor role-based access rather than storing roles in Firestore (where users could attempt to modify them). - Consider enabling multi-factor authentication for admin users via Firebase Auth MFA.
3. API Key Restrictions: A Critical and Commonly Skipped Step
Firebase API keys are visible in client-side code and in your built JavaScript bundles. This is by design — they identify your project. But unrestricted API keys allow anyone to make requests to your Firebase project from anywhere.
Restrict your keys in the Google Cloud Console:
- 1Navigate to Google Cloud Console → APIs & Services → Credentials.
- 2Click your Firebase API key.
- 3Under Application restrictions, set HTTP referrers and add only your production domain(s):
https://yourdomain.com/*. - 4Under API restrictions, restrict the key to only the APIs it needs: Firebase, Firestore, Identity Platform, etc.
Run a free ZeriFlow scan → to confirm your Firebase Hosting endpoint is properly configured and returns expected security headers.
Service account keys:
- Never commit service account JSON files to version control.
- Use Workload Identity Federation for CI/CD pipelines instead of downloading service account keys.
- Rotate service account keys that may have been exposed, and audit key usage in Cloud Audit Logs.
4. Firebase App Hosting and CSP Headers
Firebase App Hosting (and the legacy Firebase Hosting) allows you to configure HTTP headers via firebase.json. This is the correct place to add security headers — they apply at the CDN edge, before your app even loads.
{
"hosting": {
"headers": [
{
"source": "**",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" },
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self' https://www.gstatic.com https://*.firebaseapp.com; connect-src 'self' https://*.googleapis.com wss://*.firebaseio.com; frame-src 'none'; object-src 'none'"
}
]
}
]
}
}CSP considerations for Firebase apps:
- Firebase Auth's sign-in UI loads resources from
https://www.gstatic.com— include it inscript-src. - Firestore's real-time listener uses WebSocket connections to
wss://*.firebaseio.com— include inconnect-src. - Start with
Content-Security-Policy-Report-Onlyand areport-uriendpoint to catch violations before enforcing.
5. Environment Variable Management
Hardcoded credentials in client-side code — or committed to Git — are among the most common Firebase security failures.
For frontend apps (React, Vue, Next.js):
- Use
.envfiles (.env.local,.env.production) for Firebase config values. - Add
.env*.localto.gitignore— this should already be there in most framework scaffolds, but verify. - Understand that
NEXT_PUBLIC_*andVITE_*variables are bundled into client-side code — visible to anyone who opens DevTools. Firebase config values (apiKey, projectId, etc.) are safe to expose; service account keys are not.
For backend/Cloud Functions:
- Never hardcode secrets in Cloud Functions source code.
- Use Google Secret Manager to store and access secrets at runtime:
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
- Set Cloud Functions runtime config via
firebase functions:config:setfor non-sensitive config, and Secret Manager for sensitive values.
CI/CD hygiene:
- Use GitHub Actions secrets or your CI provider's secrets vault for deployment credentials.
- Rotate the Firebase CI token (
firebase login:ci) periodically. - Enable GitHub secret scanning on your repository to catch accidentally committed Firebase keys.
6. Realtime Database Rules (If You Use It)
Firebase Realtime Database has a separate rules system from Firestore. If your project uses both, both need secure rules.
The default Realtime Database rule after disabling test mode is:
{
"rules": {
".read": false,
".write": false
}
}This is the correct starting point. Build from here using auth.uid validation, similar to Firestore. Use .validate rules to enforce data types and shapes at write time.
FAQ
### Q: Are Firebase API keys safe to expose in client-side JavaScript? A: The Firebase client config (apiKey, projectId, authDomain, etc.) is designed to be public — it identifies your project, it doesn't grant admin access. The actual access control is enforced by Firestore/RTDB security rules and Firebase Auth. That said, you should still add HTTP referrer restrictions in Cloud Console to prevent your quota from being abused by third parties.
### Q: How do I test my Firestore security rules?
A: Use the Firebase Emulator Suite with @firebase/rules-unit-testing. Write unit tests that attempt reads and writes as different users (authenticated, unauthenticated, wrong UID) and assert that access is correctly granted or denied. Run these in CI before every deployment.
### Q: What's the difference between Firebase Hosting and Firebase App Hosting?
A: Firebase Hosting is the original static/SPA hosting product. Firebase App Hosting (2024+) is a newer managed platform for full-stack Next.js, Angular, and similar frameworks with server-side rendering. Both support custom headers via firebase.json or apphosting.yaml.
### Q: How do I know if my Firebase app's public endpoint is missing security headers? A: Run a ZeriFlow scan on your Firebase Hosting URL. It checks for all standard security headers (CSP, HSTS, X-Frame-Options, etc.) and shows exactly which ones are absent or misconfigured.
### Q: Should I use JWT (custom tokens) or Firebase Auth's built-in tokens? A: For most applications, Firebase Auth's built-in ID tokens are the right choice. Use custom tokens only when you need to integrate with an existing external auth system. Whichever you use, always verify tokens server-side via the Firebase Admin SDK before trusting claims.
Conclusion
Firebase's developer experience is excellent, but that same speed-optimized UX ships projects with permissive defaults that need to be actively tightened for production. Firestore rules, API key restrictions, authentication hardening, security headers in firebase.json, and disciplined environment variable management together form a comprehensive Firebase security posture.
Run an external scan to validate what an attacker sees when they hit your production URL.
Run a free ZeriFlow scan → — 60 seconds, no credit card.