Securing a FlutterFlow app means: replacing default Firestore test rules with strict per-user rules, moving all API keys to Cloud Functions so they never reach the client, enabling Firebase App Check to block fake clients, validating all user inputs server-side, and adding rate limiting on sensitive endpoints. Never deploy with the default 'allow read, write: if true' rules.
A Practical Security Checklist for FlutterFlow Apps
Most FlutterFlow apps launch with significant security holes — not because builders are careless, but because FlutterFlow's default Firebase setup prioritizes getting something working over locking it down. The number one threat is the default Firestore test rules that allow anyone on the internet to read and write your entire database. This tutorial covers the full security checklist: Firestore rules, API key management, Firebase App Check, input validation, and rate limiting. Each step is actionable in under 10 minutes and dramatically improves your app's security posture.
Prerequisites
- FlutterFlow project with Firebase connected
- Firebase console access for your project
- Firebase Authentication enabled with at least one sign-in method
- Basic understanding of Firestore collections and documents
Step-by-step guide
Replace default Firestore test rules immediately
Replace default Firestore test rules immediately
This is the most critical security fix. In the Firebase console, click Firestore Database in the left sidebar, then click the Rules tab. If you see 'allow read, write: if true;' — your database is completely public. Anyone with your project ID can read, write, and delete all your data. Replace the rules immediately with the template below that restricts all operations to authenticated users accessing only their own data. For collections that need different permissions (like public content or admin-only data), add specific rules above the default deny-all at the bottom.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Users can only read and write their own profile6 match /users/{userId} {7 allow read, write: if request.auth != null8 && request.auth.uid == userId;9 }1011 // Users can read public articles; only authors can write12 match /articles/{articleId} {13 allow read: if true;14 allow create: if request.auth != null;15 allow update, delete: if request.auth != null16 && request.auth.uid == resource.data.author_id;17 }1819 // Orders: users see only their own; admins see all20 match /orders/{orderId} {21 allow read, write: if request.auth != null22 && request.auth.uid == resource.data.user_id;23 allow read, write: if request.auth != null24 && request.auth.token.admin == true;25 }2627 // Default deny-all for everything else28 match /{document=**} {29 allow read, write: if false;30 }31 }32}Expected result: Unauthenticated requests to your Firestore database return permission-denied errors. Authenticated users can only access documents they are authorized for.
Move all API keys to Cloud Functions — never store them in FlutterFlow
Move all API keys to Cloud Functions — never store them in FlutterFlow
FlutterFlow compiles your app into JavaScript (for web) and Dart (for mobile). Any API keys you store in App State variables, Custom Actions, or widget properties are visible in the compiled app bundle — any user can extract them with basic tools. Instead, create a Firebase Cloud Function for each third-party API call (Stripe, SendGrid, OpenAI, etc.). Store API keys as Firebase environment secrets using 'firebase functions:secrets:set MY_API_KEY'. The Cloud Function reads the secret at runtime, makes the API call, and returns only the needed data to the app. In FlutterFlow, call the function using a Custom Action that hits the Cloud Function's HTTPS endpoint.
Expected result: Your app bundle contains zero API keys. All sensitive API calls are proxied through Cloud Functions that read secrets from Firebase's secure environment.
Enable Firebase App Check to block fake clients
Enable Firebase App Check to block fake clients
Firebase App Check verifies that requests to your Firebase services come from your legitimate app and not from curl, Postman, or a competitor's script. In the Firebase console, go to App Check under the Build section. Click 'Get started', select your app (iOS and Android separately), and choose the attestation provider: Play Integrity for Android and App Attest for iOS (both free). On the Web, use reCAPTCHA Enterprise. Once enabled and enforced, only verified app instances can call Firestore, Storage, and Cloud Functions. Enforcement takes effect 7 days after enabling — use the debug token feature during development to test without affecting your real usage metrics.
Expected result: The App Check dashboard shows verified attestation requests from your app. Requests from curl or unauthorized clients receive a 403 App Check token is invalid response.
Validate and sanitize all user inputs before writing to Firestore
Validate and sanitize all user inputs before writing to Firestore
FlutterFlow's form validation (required fields, email format) only runs on the client. A determined attacker can bypass the app entirely and send malformed data directly to Firestore. Add server-side validation in two places: first, in your Firestore security rules using request.resource.data conditions to enforce field types and lengths; second, in Cloud Functions that handle sensitive writes. Check string lengths (request.resource.data.username.size() < 50), reject special characters in fields that should not have them, and verify numeric fields are within acceptable ranges. Never trust that data stored in Firestore was written by your app.
1// Firestore rule example: validate article fields on create2match /articles/{articleId} {3 allow create: if request.auth != null4 && request.resource.data.title is string5 && request.resource.data.title.size() > 06 && request.resource.data.title.size() <= 2007 && request.resource.data.body is string8 && request.resource.data.body.size() <= 500009 && request.resource.data.author_id == request.auth.uid10 && request.resource.data.keys().hasOnly(11 ['title', 'body', 'tags', 'author_id', 'created_at']);12}Expected result: Firestore rejects documents that fail validation — wrong field types, oversized strings, or unexpected extra fields — even when submitted directly without going through your app.
Add rate limiting on sensitive Cloud Functions
Add rate limiting on sensitive Cloud Functions
Without rate limiting, any authenticated user can hammer your Cloud Functions thousands of times — running up your Firebase bill or spamming other users. Implement rate limiting using Firestore to track request counts per user. In your Cloud Function, before processing the request, check a rate_limits/{userId} document. If the request count in the last minute exceeds your threshold, return a rate limit error immediately. Use FieldValue.increment to update the counter atomically and set a Firestore TTL to auto-delete old rate limit documents. For RapidDev clients building payment apps, we recommend 10 requests per minute for financial Cloud Functions and 60 per minute for general data operations.
1// Cloud Function rate limiter middleware2const admin = require('firebase-admin');34async function checkRateLimit(userId, actionName, maxPerMinute = 20) {5 const db = admin.firestore();6 const key = `${userId}_${actionName}`;7 const ref = db.collection('rate_limits').doc(key);89 const now = Date.now();10 const windowStart = now - 60 * 1000;1112 const result = await db.runTransaction(async (tx) => {13 const doc = await tx.get(ref);14 const data = doc.exists ? doc.data() : { count: 0, windowStart: now };1516 // Reset window if older than 1 minute17 if (data.windowStart < windowStart) {18 tx.set(ref, { count: 1, windowStart: now, expiresAt: new Date(now + 120000) });19 return { allowed: true };20 }2122 if (data.count >= maxPerMinute) {23 return { allowed: false };24 }2526 tx.update(ref, { count: admin.firestore.FieldValue.increment(1) });27 return { allowed: true };28 });2930 return result.allowed;31}Expected result: Calling a rate-limited Cloud Function more than 20 times per minute returns a resource-exhausted error. Normal users are never affected because they naturally stay within the limit.
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 }9 function isOwner(userId) {10 return isSignedIn() && request.auth.uid == userId;11 }12 function isAdmin() {13 return isSignedIn() && request.auth.token.admin == true;14 }15 function validString(field, maxLen) {16 return field is string && field.size() > 0 && field.size() <= maxLen;17 }1819 // User profiles20 match /users/{userId} {21 allow read: if isSignedIn();22 allow create: if isOwner(userId)23 && validString(request.resource.data.display_name, 100)24 && request.resource.data.keys().hasOnly(25 ['display_name', 'email', 'created_at', 'avatar_url']);26 allow update: if isOwner(userId)27 && validString(request.resource.data.display_name, 100);28 allow delete: if isOwner(userId) || isAdmin();29 }3031 // Public content — readable by all, writable by author32 match /articles/{articleId} {33 allow read: if true;34 allow create: if isSignedIn()35 && validString(request.resource.data.title, 200)36 && request.resource.data.author_id == request.auth.uid37 && request.resource.data.keys().hasOnly(38 ['title', 'body', 'tags', 'author_id', 'created_at', 'status']);39 allow update: if isOwner(resource.data.author_id) || isAdmin();40 allow delete: if isOwner(resource.data.author_id) || isAdmin();41 }4243 // Orders — private to owner44 match /orders/{orderId} {45 allow read, write: if isOwner(resource.data.user_id);46 allow read, write: if isAdmin();47 }4849 // Admin-only collections50 match /admin_settings/{docId} {51 allow read, write: if isAdmin();52 }5354 // Default deny-all55 match /{document=**} {56 allow read, write: if false;57 }58 }59}Common mistakes when improving Your FlutterFlow App's Security
Why it's a problem: Deploying with the default Firestore test rules (allow read, write: if true)
How to avoid: Replace test rules immediately with authentication-based rules. Go to Firebase console → Firestore → Rules and deploy the secure template from this tutorial before your first real user signs up.
Why it's a problem: Storing third-party API keys in FlutterFlow App State or Custom Action code
How to avoid: Store all API keys as Firebase Function secrets and proxy every third-party API call through a Cloud Function. The function returns only the data the app needs, never the raw API key.
Why it's a problem: Relying only on client-side form validation for security
How to avoid: Add field validation to your Firestore security rules using request.resource.data type checks, size limits, and the hasOnly() keys restriction.
Why it's a problem: Enabling App Check enforcement immediately without a monitoring period
How to avoid: Enable App Check in monitoring mode first. Review the App Check metrics dashboard for 7 days to confirm no legitimate requests are being blocked, then click Enforce.
Best practices
- Never deploy with default Firestore test rules. Make rule deployment part of your pre-launch checklist.
- Use Firebase's Blaze plan and set a spending alert at $5 and $20 — unexpected cost spikes are often the first sign of unauthorized API usage.
- Enable Firebase Authentication email enumeration protection so attackers cannot discover which email addresses are registered.
- Use the hasOnly() rule check to prevent users from injecting unexpected fields like isAdmin into their own documents.
- Rotate API keys quarterly and revoke old keys immediately after any suspected breach.
- Enable Firestore audit logging (Cloud Audit Logs) in production to maintain a record of all data access for compliance.
- Test your Firestore rules using the Firebase emulator suite locally before deploying — a rules mistake can lock your own app out of its data.
- Set up Firebase Security Rules unit tests using the @firebase/rules-unit-testing package so rule changes are caught before deployment.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My FlutterFlow app uses Firebase. Review this Firestore security checklist and explain which items are most critical to implement before launch: secure rules, API key management, App Check, input validation, and rate limiting.
Update my FlutterFlow app's Firestore security rules to restrict access so users can only read and write their own documents. Add field validation to prevent oversized strings and unauthorized field injection. Show me the complete rules file.
Frequently asked questions
How do I know if my Firestore rules are currently open to the public?
Open the Firebase console, go to Firestore Database, and click the Rules tab. If you see 'allow read, write: if true;' anywhere without an authentication condition, your database is publicly accessible. Fix this immediately.
Can someone extract my API keys from my FlutterFlow app?
Yes. FlutterFlow compiles to Dart (mobile) and JavaScript (web). Both can be decompiled or inspected in browser developer tools. Any API key stored in the app code, App State, or widget properties is visible to anyone who looks. Always proxy sensitive API calls through Firebase Cloud Functions.
What is Firebase App Check and do I need it?
App Check verifies that requests to Firebase come from your actual app, not from scripts or unauthorized clients. It is not mandatory but strongly recommended for any production app. Without it, anyone who finds your Firebase config can call your Cloud Functions and query Firestore directly.
Does FlutterFlow's form validation protect my database?
No. Form validation in FlutterFlow only runs in the app UI. An attacker can skip the app entirely and send API requests directly to Firestore. You must add validation in your Firestore security rules using request.resource.data checks.
How do I set an admin role for specific users in Firebase?
Use Firebase Admin SDK in a Cloud Function to set a custom claim: auth.setCustomUserClaims(uid, { admin: true }). The user must sign out and back in for the claim to appear in their ID token. Reference it in Firestore rules with request.auth.token.admin == true.
Will stricter Firestore rules break my FlutterFlow app?
They might if your app queries data without passing the user's authentication token. Test rule changes in the Firebase Rules Playground first and use the FlutterFlow preview mode to verify all screens still load correctly after tightening rules.
How much does Firebase App Check cost?
Firebase App Check is free. Play Integrity (Android) and App Attest (iOS) attestation providers have no direct cost from Firebase. reCAPTCHA Enterprise for web has a free tier of 10,000 assessments per month.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation