The default rules problem

When you create a new Firestore or Realtime Database instance, Firebase gives you two rule options: "locked mode" (deny all) or "test mode" (allow all for 30 days). Most developers choose test mode to move fast, then forget to update it. Even when the 30-day window closes, we've seen teams re-enable open rules because "the app broke."

An open Firestore database means anyone with your project's configuration (which is public — it's in your frontend code) can read, write, and delete any document. No authentication required. For a fintech app, this means user data, transaction records, and financial details are fully exposed.

// DEFAULT TEST MODE — never use in production
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}
Pentest finding

Open Firestore rules on a savings platform

During a security assessment of a Nigerian micro-savings app, we accessed the Firestore database directly using the public Firebase config extracted from the APK. All user profiles, savings balances, and withdrawal records were readable and writable without authentication. Over 15,000 user records were exposed.

Writing proper Firestore security rules

Firestore rules should enforce three things: authentication, ownership, and data shape validation. Here's a pattern that covers all three for a typical fintech use case:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Users can only read/write their own profile
    match /users/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow write: if request.auth != null
                   && request.auth.uid == userId
                   && request.resource.data.keys().hasAll(['name', 'email'])
                   && request.resource.data.name is string
                   && request.resource.data.name.size() <= 100;
    }

    // Transactions are read-only for the owner, write-only via Cloud Functions
    match /transactions/{txId} {
      allow read: if request.auth != null
                  && resource.data.userId == request.auth.uid;
      allow write: if false; // Only Cloud Functions with admin SDK
    }

    // Deny everything else by default
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Key principles: never use wildcard allows, validate data structure in rules where possible, and restrict write access on financial records to server-side Cloud Functions using the Admin SDK (which bypasses rules). This prevents client-side manipulation of balances and transaction records.

Realtime Database rules

If you're using Realtime Database (RTDB) instead of or alongside Firestore, the same principles apply but the syntax differs. RTDB rules use JSON-style validation:

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid",
        ".validate": "newData.hasChildren(['name', 'email'])"
      }
    },
    "transactions": {
      "$txId": {
        ".read": "data.child('userId').val() === auth.uid",
        ".write": false
      }
    },
    ".read": false,
    ".write": false
  }
}

RTDB rules cascade — a parent rule that grants access cannot be overridden by a more restrictive child rule. Structure your rules so that the root denies everything, and children grant specific access. Test your rules using the Firebase Emulator Suite before deploying.

Firebase Auth: custom claims for authorization

Firebase Auth handles authentication (who is this user), but authorization (what can this user do) requires custom claims. Don't store roles or permissions in Firestore documents and then check them in rules — that requires a read operation on every request and is easy to get wrong.

Set custom claims via the Admin SDK in a Cloud Function:

// Cloud Function to set admin role
exports.setAdminRole = functions.https.onCall(async (data, context) => {
  // Verify the caller is a super-admin
  if (!context.auth?.token?.superAdmin) {
    throw new functions.https.HttpsError('permission-denied', 'Not authorized');
  }

  await admin.auth().setCustomUserClaims(data.uid, { admin: true });
  return { message: 'Admin role assigned' };
});

Then reference claims directly in security rules: request.auth.token.admin == true. Custom claims are embedded in the ID token and evaluated without additional database reads. Limit custom claims to authorization data — they're capped at 1000 bytes total.

Cloud Functions security

Cloud Functions run with admin privileges by default, bypassing all security rules. This is powerful but dangerous if the functions themselves aren't secured:

Using Firebase for your fintech app? We audit Firebase security rules, auth configuration, and Cloud Functions for the exact misconfigurations covered here.

Request a Firebase security audit

API key restrictions

Firebase API keys are not secrets — they identify your project, not authenticate requests. But unrestricted API keys can be abused for quota exhaustion, unauthorized service access, and billing attacks. Restrict them in the Google Cloud Console:

Without these restrictions, an attacker who extracts your API key from a decompiled mobile app can use it from any platform for any Firebase service associated with your project.

Firebase App Check

App Check verifies that incoming requests originate from your genuine app, not from scripts, bots, or modified APKs. It uses attestation providers (reCAPTCHA for web, Play Integrity for Android, Device Check for iOS) to validate the request source.

Enable App Check enforcement on Firestore, Realtime Database, Cloud Functions, and Cloud Storage. Without it, anyone with your Firebase config can make API calls directly — even with perfect security rules, automated abuse (scraping, enumeration, quota attacks) remains possible.

App Check is not a replacement for security rules. It's an additional layer that ensures rules are evaluated only for requests from legitimate app instances, not from curl commands or Postman.

Key takeaway

Firebase security is your responsibility, not Google's

Firebase's defaults prioritize developer speed. Production security requires explicit rules on every collection, custom claims for authorization, secured Cloud Functions, restricted API keys, and App Check enforcement. If you're building fintech on Firebase, treat these as deployment prerequisites — not post-launch improvements.

Related reading

Blog: Can hackers see your source code? · Fix broken object-level authorization · Security audit before launch

Guides: Mobile app pentesting · Security checklist · OWASP for fintech

Services: Penetration testing · API security · Secure architecture review