Debug FlutterFlow security issues systematically: use the Firebase Rules Monitor to diagnose permission denied errors, rotate exposed API keys immediately, fix App Check rejections by adding your app's debug token for development, resolve CORS errors on Cloud Functions by adding origin headers, and force token refresh for stale auth sessions. Never fix permission denied by setting Firestore rules to allow read, write.
Security Bugs Are Different From Regular Bugs — Here Is How to Debug Them
Regular app bugs produce visible errors in the UI. Security bugs often fail silently — data just does not appear, an action does nothing, or a page stays blank. This makes them harder to debug than a crash. The most dangerous debugging instinct is to remove all security rules to confirm the app works, then add rules back later. That pattern is how production apps end up permanently open. This tutorial teaches systematic security debugging for FlutterFlow: use the right Firebase tools to find the exact failure point, fix it minimally and precisely, and verify the fix without compromising other security boundaries.
Prerequisites
- FlutterFlow project connected to Firebase with Firestore and Authentication
- Access to the Firebase console (Firestore, Authentication, and Functions tabs)
- Basic understanding of Firestore Security Rules syntax
- Firebase CLI installed locally for testing Security Rules with the emulator (optional but helpful)
Step-by-step guide
Diagnose permission denied errors using the Firestore Rules Monitor
Diagnose permission denied errors using the Firestore Rules Monitor
When a Firestore read or write fails silently in FlutterFlow, the first tool to use is the Firebase console's Rules Monitor. Go to Firestore > Rules > Monitor rules. Enable monitoring and reproduce the failing action in your app. The Monitor shows every rules evaluation in real time — the request path, the requesting UID, and whether the rule allowed or denied the request. Find the denied request and look at the `allow` condition that failed. Most permission denied errors fall into three categories: (1) the user is not authenticated (`request.auth == null`), (2) the UID does not match the document's `ownerId` field, or (3) a new collection was added to the app but no rule was written for it yet. Fix the specific rule that failed — do not broaden all rules to compensate.
Expected result: You identify the exact rule condition that is failing. The fix is a targeted change to one rule, not a blanket allow.
Identify and fix data visible to the wrong users
Identify and fix data visible to the wrong users
If users can see each other's data — another user's profile, private documents, or payment history — the most common cause is Firestore rules using `allow read: if true` or a missing ownership check. In the Firebase console, go to Firestore > Rules and search for any rule that contains `if true` or `allow read;` without a condition. Replace these with explicit conditions. For user-owned data, the correct pattern is `allow read: if request.auth != null && request.auth.uid == resource.data.ownerId`. Also check list (collection-level) queries — a document-level rule that passes does not protect against a query that returns all documents in the collection. Add query constraints in your Firestore Security Rules to validate query filters match the authenticated user.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // WRONG — allows anyone to read any user document6 // match /users/{userId} {7 // allow read: if true;8 // }910 // CORRECT — user can only read their own document11 match /users/{userId} {12 allow read, write: if request.auth != null13 && request.auth.uid == userId;14 }1516 // CORRECT — user can only query their own documents17 match /orders/{orderId} {18 allow read: if request.auth != null19 && resource.data.userId == request.auth.uid;2021 // Validate query constraints match authenticated user22 allow list: if request.auth != null23 && request.query.filters['userId'] == request.auth.uid;24 }25 }26}Expected result: Querying for another user's documents returns an empty result or a permission denied error rather than their private data.
Rotate an exposed API key and audit damage
Rotate an exposed API key and audit damage
If you have accidentally committed an API key to GitHub, embedded it in a FlutterFlow API Manager header visible in the app binary, or shared it in a screenshot, treat it as compromised immediately. For Firebase API keys: go to the Google Cloud console > APIs and Services > Credentials, find the exposed key, and click Edit to restrict it by IP, referrer, or app bundle ID. Firebase web API keys cannot be fully secret (they are embedded in Firebase config), so the real protection is Firebase Security Rules and App Check — not key secrecy. For third-party API keys (Stripe, OpenAI): go to that provider's dashboard and revoke the exposed key, create a new key, and store the new key as a Firebase Function Secret. Move the API call from FlutterFlow API Manager to a Cloud Function.
Expected result: The old key is revoked. The new key is stored only in Firebase Function Secrets. No API calls are made directly from FlutterFlow to the third-party service.
Resolve Firebase App Check rejecting legitimate requests
Resolve Firebase App Check rejecting legitimate requests
Firebase App Check verifies that requests to Firebase services come from your legitimate app, not scripts or bots. When enabled, it can reject requests from legitimate sources in two common cases: (1) during FlutterFlow development, the preview runs in a browser environment that does not have your app's debug attestation token, and (2) after updating your app's bundle ID or SHA certificate fingerprint without updating App Check. For development, go to the Firebase console > App Check > Apps, find your app, and click Add debug token. Copy the debug token and add it to your FlutterFlow app's App Check configuration. For production issues, ensure your iOS app's App Attest key ID and Android app's Play Integrity API are correctly configured in App Check settings.
Expected result: FlutterFlow preview and development builds can access Firebase services while App Check remains enabled for production.
Fix CORS errors on Cloud Functions called from FlutterFlow
Fix CORS errors on Cloud Functions called from FlutterFlow
Cross-Origin Resource Sharing (CORS) errors appear when your FlutterFlow web app calls a Cloud Function that does not include the correct `Access-Control-Allow-Origin` response header. In the Cloud Function, install the `cors` npm package and wrap your HTTP function handler. For callable functions (`onCall`), Firebase handles CORS automatically — you only need to add CORS headers for HTTP functions (`onRequest`). If you are seeing CORS errors on a callable function, the issue is usually not CORS — it is an authentication error or function not found. Check the browser console for the actual error message behind the CORS error.
1// For onRequest HTTP functions that need CORS2const { onRequest } = require('firebase-functions/v2/https');3const cors = require('cors')({ origin: true });45exports.myHttpFunction = onRequest((req, res) => {6 cors(req, res, () => {7 // Handle request after CORS headers are set8 if (req.method === 'OPTIONS') {9 res.status(204).send('');10 return;11 }12 res.json({ message: 'Hello' });13 });14});1516// onCall functions handle CORS automatically — no cors package needed17// If you see a CORS error on onCall, check:18// 1. Is the function deployed? (test with firebase functions:list)19// 2. Is the user authenticated? (onCall returns 401 without auth)20// 3. Is the function name spelled correctly in FlutterFlow?Expected result: Cloud Function calls from the FlutterFlow web preview and deployed web app succeed without CORS errors.
Fix stale authentication tokens causing intermittent permission errors
Fix stale authentication tokens causing intermittent permission errors
Firebase ID tokens expire after one hour. In a long-running FlutterFlow session, an expired token causes Firestore reads and Cloud Function calls to fail with permission denied or unauthenticated errors. FlutterFlow's Firebase integration automatically refreshes tokens in the background for most cases, but issues can occur on mobile after the app is backgrounded for a long time. In FlutterFlow, add an action on the App Foreground event (Settings > App Settings > On App Foreground) that calls a Custom Action to force-refresh the Firebase ID token: `await FirebaseAuth.instance.currentUser?.getIdToken(true)`. The `true` parameter forces a refresh even if the current token is not yet expired. Also check that your Cloud Functions are not caching old auth tokens — if you pass the token as a header manually, refresh it before each request.
1// Custom Action: refreshAuthToken2import 'package:firebase_auth/firebase_auth.dart';34Future refreshAuthToken() async {5 final user = FirebaseAuth.instance.currentUser;6 if (user != null) {7 await user.getIdToken(true); // force = true refreshes immediately8 }9}Expected result: Users who return to the app after several hours no longer experience permission denied errors on their first action.
Complete working example
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Helper functions6 function isSignedIn() {7 return request.auth != null;8 }910 function isOwner(ownerId) {11 return isSignedIn() && request.auth.uid == ownerId;12 }1314 function isAdmin() {15 return isSignedIn() &&16 get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';17 }1819 // Users can only read/write their own user document20 match /users/{userId} {21 allow read: if isSignedIn() && request.auth.uid == userId;22 allow create: if isSignedIn() && request.auth.uid == userId;23 allow update: if isOwner(userId);24 allow delete: if false; // Never allow user deletion from client25 }2627 // Orders: user can read their own, admin can read all28 match /orders/{orderId} {29 allow read: if isOwner(resource.data.userId) || isAdmin();30 allow create: if isSignedIn()31 && request.resource.data.userId == request.auth.uid;32 allow update: if isAdmin();33 allow delete: if false;34 }3536 // Shared documents: owner or explicitly shared users37 match /documents/{docId} {38 allow read: if isSignedIn() && (39 resource.data.ownerId == request.auth.uid ||40 request.auth.uid in resource.data.sharedWith41 );42 allow write: if isOwner(resource.data.ownerId);43 }4445 // Deny everything else explicitly46 match /{document=**} {47 allow read, write: if false;48 }49 }50}Common mistakes
Why it's a problem: Fixing permission denied errors by changing Firestore rules to `allow read, write: if true`
How to avoid: Use the Firebase Rules Monitor to find the specific rule that is failing. Fix only that rule with the minimum necessary permissions. Use the Rules Playground to verify the fix works before deploying.
Why it's a problem: Seeing a CORS error and assuming it is a CORS configuration problem on the Cloud Function
How to avoid: Open the browser DevTools Network tab and look at the actual request and response, not just the CORS error in the Console. A 404 on the function URL means it is not deployed. A 401 means authentication is failing. Only a 200 preflight response with missing Access-Control headers is a true CORS error.
Why it's a problem: Disabling Firebase App Check to fix development errors rather than adding a debug token
How to avoid: Keep App Check enabled. For development environments, add a debug token in the Firebase console under App Check > Apps > Add debug token. Use this token only for development — never embed it in production builds.
Best practices
- Use the Firebase Rules Monitor and Playground before changing any rules — understand the exact failure before writing a fix.
- Never set Firestore rules to `allow read, write: if true`, even temporarily during debugging — use a test user account with the proper UID instead.
- Add an explicit deny-all rule at the bottom of your Firestore rules: `match /{document=**} { allow read, write: if false; }` — this catches any collections not yet covered by specific rules.
- Store all third-party API keys as Firebase Function Secrets — rotate any key that has appeared in source code, environment variables in the client, or screenshots.
- Add a token refresh call on app foreground to prevent stale auth tokens from causing intermittent permission errors after long background sessions.
- Test Security Rules changes using the Firebase Emulator locally before deploying to production — `firebase emulators:start` runs a local rules evaluator.
- Enable Firebase App Check in production and use debug tokens for development — do not disable it to fix development errors.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My FlutterFlow app uses Firestore and I am getting permission denied errors when users try to read their own profile documents. I have a `users` collection where each document is keyed by the user's Firebase UID and has an `ownerId` field equal to the UID. My current rules are `allow read: if request.auth != null && resource.data.ownerId == request.auth.uid`. The error occurs even when the user is signed in. What are all the possible reasons this rule could fail and how do I debug each one using the Firebase Rules Monitor and Playground?
My FlutterFlow app calls Firebase Cloud Functions and works fine on mobile but I get CORS errors when testing on the web in the FlutterFlow preview. The functions are `onCall` callable functions, not HTTP functions. I am not setting any custom CORS headers. What are the most likely causes of CORS errors on Firebase callable functions and how do I diagnose the real underlying error?
Frequently asked questions
How do I know if my Firestore rules are too permissive?
In the Firebase Rules Playground, try to read or write a document as a different user than the one who owns it. If the Playground allows the request, your rules are too permissive. Also check for any rule containing `if true` or no condition at all — these grant unrestricted access. Run the Firebase Security Rules audit tool at security.rules.firebase.google.com for automated suggestions.
My users are getting signed out randomly. What causes this?
The most common causes are: (1) token refresh failing because the device is offline when the token expires, (2) the user's account being disabled or deleted in Firebase Authentication, or (3) calling `FirebaseAuth.instance.signOut()` accidentally in an error handler. Check your FlutterFlow auth routing — the 'Entry Page' setting should handle unauthenticated sessions gracefully rather than signing users out.
How do I test Firestore Security Rules without deploying them?
Use the Firebase Local Emulator. Run `firebase emulators:start` which starts a local Firestore emulator with rules evaluation. Point your FlutterFlow app to the emulator by setting the Firestore emulator host in your app's Firebase initialization. The emulator shows rule evaluation details in its UI. You can also use the Rules Playground in the Firebase console to test rules against simulated requests without affecting production.
What is the difference between a Firebase API key and a service account key?
A Firebase API key (the web config key starting with `AIza...`) is designed to be public-facing — it identifies your project and can be safely included in client-side code. Its access is controlled by Firebase Security Rules and App Check. A service account key (a JSON file with a private key) grants admin-level access bypassing all Security Rules. Service account keys must never be included in client apps or committed to GitHub — use them only in Cloud Functions running on Google Cloud infrastructure.
Why does my Cloud Function return 401 even when the user is signed in?
Callable Cloud Functions receive the auth context automatically if the Firebase SDK is initialized correctly in the client. Common causes of 401 on callable functions: (1) the function is being called before the user completes sign-in, (2) the Firebase SDK is not initialized with the same project as the function, or (3) App Check is enabled and the debug token is not configured. Log `request.auth` at the start of the function to see what authentication context the function actually receives.
Should I use Firebase App Check or Firestore Security Rules — or both?
Both, for different threats. App Check verifies that requests come from your legitimate app binary — it blocks bots, scripts, and other apps from calling your Firebase services. Firestore Security Rules verify that the authenticated user is allowed to read or write a specific document — they enforce data access control. App Check does not replace Security Rules, and Security Rules do not replace App Check. Enable both in production.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation