← Stack guides

Firebase

Security for your Firebase app

Firebase security depends almost entirely on the rules you write. With rules in test mode or too open, your database is within anyone's reach. Review it before opening your app.

Typical risks in Firebase

Rules in test mode

Test mode allows read and write for everyone. Many people launch without changing it, leaving the database wide open.

Mistaking the public key for security

The Firebase config is public by design; what protects your data are the rules, not hiding that key. Assuming otherwise leads to exposed data.

Other keys in the frontend

Alongside the Firebase config, keys that are actually secret often sneak in (payments, email, AI). Those must never be in browser JavaScript.

"Firebase security" is two things worth keeping apart. The first is Firebase's model: the browser talks directly to the database and the storage, so there is no server filtering anything, and protection lives in the rules, in App Check and in the project configuration. The second is what Lovable, Bolt or Cursor do with Firebase when they generate code, which is where almost all the holes come from. This guide explains the model, goes through what AI gets wrong, leaves you a firebase.json and a base ruleset to paste, a review prompt and the check from outside. The ten most common rule mistakes, with the bad rule and the good one, are in the most common security mistakes in Firebase.

Part 1: how security works in Firebase

The client talks to the services, not to your server

In a typical Firebase app there is no backend of your own: the browser SDK reads and writes Firestore, Realtime Database and Storage directly. That means the config object with apiKey and projectId is public by design (it identifies the project, it authorizes nothing) and the only barrier between a visitor and your data is the security rules. Each product has its own: Firestore, Realtime Database and Storage are configured separately, and an app usually has open whichever one nobody looked at.

Rules answer "who can do what"

A rule receives the request with the authenticated user (request.auth), the document being read or written and the new data (request.resource.data). With that it decides. The key distinction is between authenticated (there is a session) and authorized (that session may see that document): request.auth != null only checks the first, and signing up is free. Rules also validate what gets written, so a user cannot set isAdmin: true on their own profile.

What protects you besides the rules

  • App Check. Requires requests to come from your real app (via reCAPTCHA or Play Integrity) and not from a script with your projectId. It stops abuse and the unexpected bills from mass reads.
  • API key restriction. In the Google Cloud console, restrict the key to your site's domains and to the APIs you use. It does not replace the rules, but it closes a door.
  • Auth. Authorized domains for sign-in, email enumeration protection, email verification, only the sign-in methods you actually use, multi-factor authentication if you handle sensitive data.
  • Cloud Functions as the server. Anything that needs a secret key (payments, email, AI) goes in a function, with the secret in Secret Manager, and the client calls the function. Careful: an HTTP function is public to the whole internet unless you verify the user's token or use a callable function with App Check.
  • Budget. Firebase charges per read and per invocation. Budget alerts in Google Cloud and limits in the rules (request.query.limit) keep a loop or an attacker from leaving you a bill.

Hosting: what Firebase sets and what it does not

Firebase Hosting gives you automatic TLS, a CDN and the redirect to HTTPS. It does not set security headers: they are added in firebase.json. And it serves everything in the public folder, including a .env copied there by mistake.

Part 2: what AI gets wrong with Firebase

  • Test mode rules, then if true. The AI leaves the rules that open everything for thirty days; when they expire, the app breaks and the quick fix is to open them forever.
  • request.auth != null as the only condition. Any registered user reads everyone else's data.
  • Unvalidated writes. The profile has role or credits and the update rule does not exclude those fields.
  • Other keys in the bundle. Next to the Firebase config appear the Stripe secret key, the OpenAI key or an email service key. Detail in secrets your app leaks in the browser.
  • HTTP functions that do not check the user. The function that charges, sends emails or calls a model answers anyone who knows the URL.
  • Storage with rules copied from Firestore. allow read, write: if request.auth != null in storage.rules: any user reads other people's files and uploads whatever they want.
  • No App Check, no headers, no alerts. Three boxes nobody ticks because the app "already works".

A block to paste into your project

Security headers in firebase.json (the Content-Security-Policy goes separately because it depends on your scripts):

{
  "hosting": {
    "public": "dist",
    "headers": [
      {
        "source": "**",
        "headers": [
          { "key": "Strict-Transport-Security", "value": "max-age=63072000; 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=()" }
        ]
      }
    ]
  }
}

And a starting point for firestore.rules: everything closed by default, each user their own document and privilege fields protected.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null && request.auth.uid == userId
        && !request.resource.data.keys().hasAny(['role', 'isAdmin', 'plan', 'credits']);
      allow update: if request.auth != null && request.auth.uid == userId
        && !request.resource.data.diff(resource.data).affectedKeys()
           .hasAny(['role', 'isAdmin', 'plan', 'credits']);
    }
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Then add one explicit rule per collection; the last one, with if false, guarantees that any new collection is born closed.

A review prompt before publishing

Review the security of this Firebase app and do not change anything yet. Answer with a list:
1. Firestore, Realtime Database and Storage rules that allow access to anyone, with a test-mode date or with request.auth != null as the only condition.
2. Write rules that do not validate fields, especially role, isAdmin, plan or credits.
3. Keys other than the Firebase config present in browser code, with file and line.
4. HTTP Cloud Functions that do not verify the user's token, and functions that read secrets from code instead of Secret Manager.
5. Whether App Check is enabled and whether the API key is restricted by domain.
6. Security headers in firebase.json and files in the public folder that should not be there.
For each point, propose the exact rule or change and wait for my confirmation.

Check the result from the outside

Your projectId is in the bundle, so anyone can do this. The Firestore REST API without a token:

curl -s "https://firestore.googleapis.com/v1/projects/YOUR_PROJECT/databases/(default)/documents/users" | head -c 400
curl -sI https://yourdomain.com | grep -iE "strict-transport|x-frame|x-content-type"

The right outcome is a permissions error on the first (if it returns documents, the collection is open) and the headers on the second. Repeat the first for every collection and for Storage. Scanaris runs these checks and about forty more in 30 seconds: it detects the Firebase config in your bundle, probes the usual collections without authentication, looks for other keys in the JavaScript, reviews headers, CORS, cookies and exposed files, and gives you a fix prompt for each finding to paste into Lovable, Bolt or Cursor. If your app uses Supabase instead of Firebase, the Supabase guide is the equivalent.

Frequently asked questions

Do I have to hide the Firebase apiKey?

No. It is public by design and identifies the project. What protects the data are the rules and App Check. What is worth doing is restricting it to your domains in Google Cloud.

Are Cloud Functions private?

An HTTP function is a public URL. Verify the user's token inside the function or use callable functions with App Check. Functions that react to events (a document created, a file uploaded) have no URL and are internal.

How do I test the rules without breaking the app?

With the console simulator (Rules tab) to test specific reads, and with the local emulator for automated tests. Change the rules with the app open in another tab and check that what should work still works.

Related checks

Does your Firebase app pass these checks?

Scan it free and get a fix prompt for every issue we find.

Scan my site free