Skip to main content
Back to blog
April 20, 2026·Updated May 1, 2026|8 min read|Anay Pandya

Firebase Security Best Practices: Rules, Keys, and Headers

Firebase's default configuration is optimized for development speed, not production security. Here's how to close every gap before you go live.

Anay Pandya

2,074 words

AP

Anay Pandya

Founder of ZeriFlow · 10 years fullstack engineering · About the author

Key Takeaways

  • Firebase's default configuration is optimized for development speed, not production security. Here's how to close every gap before you go live.
  • Includes copy-paste code examples and step-by-step instructions.
  • Free automated scan available to verify your implementation.

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.

<div class="zf-stat-callout" style="background:#0d1117;border:1px solid rgba(16,185,129,0.25);border-left:3px solid #10b981;border-radius:4px;padding:16px 20px;margin:24px 0"> <p style="margin:0 0 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:#10b981;font-family:monospace">ZeriFlow Data — 12,400+ sites analyzed</p> <p style="margin:0;font-size:13px;color:#e2e8f0;line-height:1.6;font-family:monospace">ZeriFlow data: the average site passes only 4 out of 11 check your security headers checks. The most commonly missing are Content-Security-Policy (64% absent), Permissions-Policy (77% absent), and HSTS (59% absent).</p> </div>

Is your site actually secure?

Run a free check — 60 seconds

Scan free →
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:

javascript
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 != null before granting any access.
  • Use request.auth.uid == resource.data.ownerId to 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 idToken on your backend for any server-side operations. Don't trust client-provided UIDs.

[Session management](https://zeriflow.com/blog/session-fixation-attack-prevention):

  • Firebase ID tokens expire after 1 hour. Refresh tokens are long-lived — treat them like passwords.
  • Use auth.currentUser.getIdTokenResult() to check claims for role-based access rather than storing roles in Firestore (where users could attempt to modify them).
  • Consider enabling [multi-factor authentication](https://zeriflow.com/blog/multi-factor-authentication-mfa-guide) 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:

  1. 1Navigate to Google Cloud Console → APIs & Services → Credentials.
  2. 2Click your Firebase API key.
  3. 3Under Application restrictions, set HTTP referrers and add only your production domain(s): https://yourdomain.com/*.
  4. 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.

json
{
  "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 in script-src.
  • Firestore's real-time listener uses WebSocket connections to wss://*.firebaseio.com — include in connect-src.
  • Start with Content-Security-Policy-Report-Only and a report-uri endpoint 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 .env files (.env.local, .env.production) for Firebase config values.
  • Add .env*.local to .gitignore — this should already be there in most framework scaffolds, but verify.
  • Understand that NEXT_PUBLIC_* and VITE_* 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:
javascript
  const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
  
  • Set Cloud Functions runtime config via firebase functions:config:set for 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:

json
{
  "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.


Further Reading

<!-- zf-internal-links -->

Firebase App Check — Protecting Your Backend

Firebase App Check verifies that requests to your backend come from your legitimate app, not from unauthorized clients or scrapers.

Enable App Check in your Firebase console, then initialize it in your app:

javascript
import { initializeApp } from 'firebase/app';
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';

const app = initializeApp(firebaseConfig);

const appCheck = initializeAppCheck(app, {
  provider: new ReCaptchaV3Provider('YOUR_RECAPTCHA_SITE_KEY'),
  isTokenAutoRefreshEnabled: true
});

Enforce App Check in your Firestore security rules:

javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.app != null;
    }
  }
}

App Check reduces abuse of your Firebase endpoints by requiring a valid attestation token with every request. This is particularly important for publicly accessible apps where unauthenticated endpoints could otherwise be scraped or abused at scale.

Auditing Firebase Security Rules Regularly

Security rules that were appropriate at launch can become insufficient as your data model grows. A collection added six months ago may have been left with overly permissive rules because no one revisited the Firestore console after the initial deployment.

Audit your rules on a fixed schedule — quarterly at minimum, or after any significant schema change. The Firebase console includes a Rules Playground that lets you simulate read and write operations against your rule set without touching production data. Use it to verify that:

  • Unauthenticated users cannot read or write any user-specific collections
  • Users can only modify documents where request.auth.uid matches the document owner field
  • Admin-only collections reject all client-side write attempts

For teams managing multiple Firebase projects across staging and production environments, store your security rules in version control and deploy them through CI/CD rather than editing them manually in the console. This creates an auditable history of every rule change.

Securing Firebase Hosting Headers

If you are using Firebase Hosting, security headers are configured in firebase.json under the headers key. Many Firebase projects ship with no headers configured at all, relying entirely on Firebase's defaults — which do not include a Content-Security-Policy, X-Frame-Options, or Permissions-Policy.

A minimal secure headers configuration for Firebase Hosting:

json
{
  "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": "DENY" },
          { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
          { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
        ]
      }
    ]
  }
}

Continuous Security Monitoring

Firebase security configuration can drift over time — especially as team members adjust rules, add new collections, or modify hosting settings.

Run a ZeriFlow scan on your Firebase-hosted application after every major deployment to verify that: - Security headers are correctly configured in firebase.json - TLS configuration has not regressed - No new information disclosure vectors have appeared

Set up ZeriFlow CI/CD integration to scan automatically on every merge to production, giving you a continuous security baseline rather than a point-in-time snapshot.

Check if your Firebase app's headers are configured correctly.

Free security scan — no signup required.

Related resources

Keep improving your website security

Run free scan

Related articles

Keep reading