# How to Manage Large-Scale User Authentication with Firebase in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 60-90 min
- Compatibility: FlutterFlow Free+ for UI; Firebase Blaze plan required for Cloud Functions and App Check enforcement
- Last updated: March 2026

## TL;DR

Scaling Firebase Authentication in FlutterFlow beyond 10,000 users requires four additions to the default setup: custom claims for role-based access control (so Firestore rules can enforce permissions without querying a users collection), Firebase App Check to prevent automated abuse, structured session management via Firestore, and account linking to handle users who sign up with multiple providers. Never create a separate Firebase project per tenant — use custom claims for multi-tenancy.

## What Changes at 10,000+ Users

The default FlutterFlow Firebase Auth setup (email/password + Google sign-in, Firestore user document) works well up to a few thousand users. At scale, several problems emerge. Firestore security rules that query the users collection on every read request become a bottleneck — these queries count against your read quota and add latency. Rate limits on Firebase Auth operations become relevant: 1,000 creates per second, 3,000 sign-ins per second. User management operations (bulk password resets, disabling accounts, auditing) become painful through the Firebase Console alone. And credential stuffing attacks — automated scripts trying leaked email/password combinations — can exhaust your Auth quota. Each of these problems has a specific architectural solution that plugs into FlutterFlow without requiring a full platform change.

## Before you start

- FlutterFlow project with Firebase Authentication enabled and at least one auth provider configured
- Firebase Blaze plan (required for Cloud Functions and App Check)
- Familiarity with Firestore security rules syntax
- A users Firestore collection with UID as the document ID

## Step-by-step guide

### 1. Add custom claims for role-based access control

Custom claims are key-value pairs embedded in a user's Firebase ID token. They are set server-side via the Admin SDK and are available in both your app and Firestore security rules without any additional database read. Create a Cloud Function called setUserRole that accepts a UID and a role string, and uses admin.auth().setCustomUserClaims(uid, { role }) to set the claim. Call this function from your admin panel whenever you promote a user. In your Firestore security rules, check request.auth.token.role instead of querying your users collection: allow read: if request.auth.token.role == 'admin'. In FlutterFlow, force a token refresh after role changes using IdToken refresh so the new claim takes effect immediately.

```
// functions/setUserRole.js
const { onCall } = require('firebase-functions/v2/https');
const { getAuth } = require('firebase-admin/auth');
const { initializeApp } = require('firebase-admin/app');

initializeApp();

const VALID_ROLES = ['user', 'moderator', 'admin', 'enterprise'];

exports.setUserRole = onCall(async (request) => {
  // Only admins can set roles
  if (request.auth?.token?.role !== 'admin') {
    throw new Error('Unauthorized');
  }
  const { targetUid, role } = request.data;
  if (!VALID_ROLES.includes(role)) {
    throw new Error(`Invalid role: ${role}`);
  }
  await getAuth().setCustomUserClaims(targetUid, { role });
  return { success: true, uid: targetUid, role };
});
```

**Expected result:** Users have a role claim in their ID token. Firestore rules enforce access based on the claim without querying the users collection.

### 2. Enable Firebase App Check to block automated abuse

App Check verifies that requests come from your legitimate app, not from automated scripts or emulators. In Firebase Console > App Check, register your app with the appropriate attestation provider: Play Integrity (Android), DeviceCheck or App Attest (iOS). Enable App Check enforcement for Firebase Authentication. In FlutterFlow add the firebase_app_check package to your custom code dependencies and initialize it with your platform's provider in your app's startup Custom Action. Once enforced, sign-in requests from emulators, scripts, or unofficial clients are rejected before they count against your quota. This alone can reduce credential stuffing attacks by over 95%.

```
// Custom Action: initAppCheck.dart
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:flutter/foundation.dart';

Future<void> initAppCheck() async {
  await FirebaseAppCheck.instance.activate(
    androidProvider: kDebugMode
        ? AndroidProvider.debug
        : AndroidProvider.playIntegrity,
    appleProvider: kDebugMode
        ? AppleProvider.debug
        : AppleProvider.deviceCheck,
    webProvider: ReCaptchaV3Provider('YOUR_SITE_KEY'),
  );
}
```

**Expected result:** Automated sign-in attempts from scripts and emulators are rejected by Firebase before reaching your Auth quota.

### 3. Implement structured session management in Firestore

At scale you need visibility into active sessions and the ability to revoke them server-side. Firebase Auth tokens expire after 1 hour and refresh automatically, but the refresh token itself does not expire until revoked. Create a sessions Firestore collection where each document has: userId, createdAt, lastActiveAt, deviceInfo, and isRevoked. Write a session document on login via a Cloud Function (or Custom Action). Update lastActiveAt on each app open. To force sign-out a user (e.g., on account compromise), call admin.auth().revokeRefreshTokens(uid) from a Cloud Function and set isRevoked: true on their session document. In FlutterFlow check the session document on app load and force sign-out if isRevoked is true.

```
// functions/revokeUserSession.js
const { onCall } = require('firebase-functions/v2/https');
const { getAuth } = require('firebase-admin/auth');
const { getFirestore } = require('firebase-admin/firestore');

exports.revokeUserSession = onCall(async (request) => {
  if (request.auth?.token?.role !== 'admin') {
    throw new Error('Unauthorized');
  }
  const { targetUid, reason } = request.data;
  await getAuth().revokeRefreshTokens(targetUid);
  // Mark all sessions as revoked
  const db = getFirestore();
  const sessionsSnap = await db.collection('sessions')
    .where('userId', '==', targetUid)
    .where('isRevoked', '==', false).get();
  const batch = db.batch();
  sessionsSnap.docs.forEach(doc => {
    batch.update(doc.ref, {
      isRevoked: true,
      revokedAt: new Date(),
      revokedReason: reason,
    });
  });
  await batch.commit();
  return { revoked: sessionsSnap.size };
});
```

**Expected result:** Admin can force sign-out any user. The user is redirected to the login screen within seconds of revocation.

### 4. Handle account linking for users with multiple sign-in providers

At scale, a common issue is users who sign up with Google, then later try to sign in with email/password using the same email address — Firebase throws an 'account-exists-with-different-credential' error. Handle this gracefully by catching the error in a FlutterFlow Custom Action, fetching the sign-in methods for that email, and prompting the user to sign in with the existing provider first, then link the new credential. Use FirebaseAuth.instance.currentUser?.linkWithCredential() after the initial sign-in to attach the new provider to the existing account. Store linked providers in the user's Firestore document so your admin panel can show which sign-in methods each user has.

```
// Custom Action: handleAccountLinking.dart
import 'package:firebase_auth/firebase_auth.dart';

Future<String> getSignInMethodsForEmail(String email) async {
  try {
    final methods = await FirebaseAuth.instance
        .fetchSignInMethodsForEmail(email);
    return methods.join(','); // e.g., 'google.com,password'
  } catch (e) {
    return 'error:$e';
  }
}

Future<bool> linkEmailPasswordToCurrentUser(
    String email, String password) async {
  try {
    final credential = EmailAuthProvider.credential(
        email: email, password: password);
    await FirebaseAuth.instance.currentUser
        ?.linkWithCredential(credential);
    return true;
  } on FirebaseAuthException catch (e) {
    // credential-already-in-use means another account has this email
    return false;
  }
}
```

**Expected result:** Users who sign up with multiple providers have a single unified account. No duplicate user documents are created in Firestore.

### 5. Perform bulk user operations via the Admin SDK

At scale you need to do bulk operations: import 10,000 users from a migration, disable all users from a compromised batch, or export all user emails for a compliance audit. Firebase Console handles up to a few hundred users before becoming impractical. Create admin Cloud Functions for each operation. For bulk import use admin.auth().importUsers() with up to 1,000 users per call. For bulk updates query your users collection in batches and call setCustomUserClaims or updateUser for each. For export use admin.auth().listUsers() which returns 1,000 users per page — loop through pages and accumulate results. Expose these operations from a secure admin page in FlutterFlow accessible only to users with role == 'admin' in their custom claim.

```
// functions/bulkUserExport.js
const { onCall } = require('firebase-functions/v2/https');
const { getAuth } = require('firebase-admin/auth');

exports.exportAllUsers = onCall(async (request) => {
  if (request.auth?.token?.role !== 'admin') {
    throw new Error('Unauthorized');
  }
  const users = [];
  let pageToken;
  do {
    const result = await getAuth().listUsers(1000, pageToken);
    result.users.forEach(user => {
      users.push({
        uid: user.uid,
        email: user.email,
        createdAt: user.metadata.creationTime,
        lastSignIn: user.metadata.lastSignInTime,
        disabled: user.disabled,
        customClaims: user.customClaims,
      });
    });
    pageToken = result.pageToken;
  } while (pageToken);
  return { count: users.length, users };
});
```

**Expected result:** Admins can export all user data, perform bulk updates, and manage users at scale without using the Firebase Console.

## Complete code example

File: `functions/authManagement.js`

```javascript
const { onCall } = require('firebase-functions/v2/https');
const { getAuth } = require('firebase-admin/auth');
const { getFirestore, FieldValue } = require('firebase-admin/firestore');
const { initializeApp } = require('firebase-admin/app');

initializeApp();

const requireAdmin = (request) => {
  if (request.auth?.token?.role !== 'admin') {
    throw new Error('Unauthorized: admin role required');
  }
};

exports.setUserRole = onCall(async (request) => {
  requireAdmin(request);
  const { targetUid, role } = request.data;
  const validRoles = ['user', 'moderator', 'admin', 'enterprise'];
  if (!validRoles.includes(role)) throw new Error('Invalid role');
  await getAuth().setCustomUserClaims(targetUid, { role });
  await getFirestore().collection('users').doc(targetUid)
    .update({ role, roleUpdatedAt: FieldValue.serverTimestamp() });
  return { success: true };
});

exports.revokeUserSession = onCall(async (request) => {
  requireAdmin(request);
  const { targetUid, reason } = request.data;
  await getAuth().revokeRefreshTokens(targetUid);
  const db = getFirestore();
  const snap = await db.collection('sessions')
    .where('userId', '==', targetUid)
    .where('isRevoked', '==', false).get();
  const batch = db.batch();
  snap.docs.forEach(d => batch.update(d.ref, {
    isRevoked: true,
    revokedAt: FieldValue.serverTimestamp(),
    revokedReason: reason || 'admin_action',
  }));
  await batch.commit();
  return { revoked: snap.size };
});

exports.exportAllUsers = onCall(async (request) => {
  requireAdmin(request);
  const users = [];
  let pageToken;
  do {
    const result = await getAuth().listUsers(1000, pageToken);
    result.users.forEach(u => users.push({
      uid: u.uid, email: u.email,
      createdAt: u.metadata.creationTime,
      lastSignIn: u.metadata.lastSignInTime,
      disabled: u.disabled, role: u.customClaims?.role || 'user',
    }));
    pageToken = result.pageToken;
  } while (pageToken);
  return { count: users.length, users };
});
```

## Common mistakes

- **Creating a separate Firebase project per client or tenant for multi-tenancy** — Firebase has a 30-project limit per account. Managing dozens of projects for clients creates an operational nightmare: separate deployments, separate monitoring, separate billing, and no shared tooling. Fix: Use custom claims for multi-tenancy. Add an orgId claim to each user's token. Scope all Firestore queries and security rules by orgId. Use Firebase Auth multi-tenancy (Identity Platform) for full tenant isolation if required by compliance.
- **Checking user roles by querying the Firestore users collection in security rules** — Firestore security rules that call get() to fetch a user document on every read request double your Firestore read costs and add 10-50ms latency to every query. Fix: Use custom claims in Firebase ID tokens. Claims are embedded in the JWT and available in security rules as request.auth.token.role without any database read.
- **Not forcing ID token refresh after updating custom claims** — Custom claims are embedded in the ID token when it is issued. The token is cached for up to 1 hour. If you set a new role, the user will not see it until the old token expires or is refreshed. Fix: After calling setCustomUserClaims, signal the client to refresh its token. In FlutterFlow add a Custom Action that calls FirebaseAuth.instance.currentUser?.getIdToken(true) to force a fresh token.

## Best practices

- Use Firebase Auth's built-in rate limiting as a first line of defense, then add App Check for application-level attestation.
- Store custom claim values that are checked frequently (role, planTier) in claims, and detailed profile data in Firestore — never put large objects in claims.
- Run a weekly Cloud Function that audits users with elevated roles (admin, enterprise) and sends a report to your security contact.
- Implement IP-based suspicious login detection: if a user signs in from a new country within 24 hours of a previous sign-in, log the event and optionally require re-authentication.
- Use Firebase Auth email enumeration protection (enabled in Firebase Console > Auth > Settings) to prevent attackers from discovering registered email addresses.
- Test your Firestore security rules with the Firebase Rules Playground after every rule change — a misconfigured rule can silently block your own users.
- Keep an audit log of all admin operations (role changes, session revocations, bulk imports) in a Firestore admin_audit collection for compliance and debugging.

## Frequently asked questions

### What is the maximum number of users Firebase Authentication supports?

Firebase Authentication does not have a published hard user limit. It is designed to scale to hundreds of millions of users. The limits that matter at scale are operational: 1,000 user creations per second and 3,000 sign-ins per second per project. For typical FlutterFlow apps these limits are not a concern.

### How long does it take for custom claims to take effect after being set?

Custom claims are set immediately in Firebase's backend, but the user's current ID token will not contain the new claims until it is refreshed. ID tokens are refreshed automatically after 1 hour. For immediate effect, call FirebaseAuth.instance.currentUser?.getIdToken(true) to force a refresh.

### Can I have multiple roles in custom claims?

Yes. Custom claims can be any JSON object within the 1000-byte limit. You can use an array of roles: { roles: ['admin', 'moderator'] } and check request.auth.token.roles.hasAny(['admin']) in Firestore rules. However, simpler is usually better — a single role string is easier to reason about and debug.

### What does Firebase App Check protect against?

App Check verifies that API calls come from your legitimate app binary on a real device. It protects against: credential stuffing (automated scripts testing leaked passwords), unauthorized API access from non-app clients, and scraping of your Firebase data. It does not protect against compromised legitimate devices.

### How do I handle users who delete their Firebase Auth account but still have Firestore data?

Use a Firebase Auth onDelete Cloud Function trigger: functions.auth.user().onDelete(). When a user deletes their account, the function automatically fires and can clean up their Firestore documents, Storage files, and any other associated data. Set this up from the start — orphaned data accumulates quickly at scale.

### Can RapidDev help with migrating a large user base to Firebase?

Yes. RapidDev has migrated large user bases (10,000+ accounts) to Firebase Auth using the Admin SDK's importUsers function with password hash preservation. We handle the migration planning, hash format conversion, and validation testing. Contact us if you are moving from Auth0, Cognito, or a custom auth system.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-manage-large-scale-user-authentication-with-firebase-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-manage-large-scale-user-authentication-with-firebase-in-flutterflow
