Backend as a Service does not remove architecture
Firebase accelerates identity, real-time data, files, and push. The risk is allowing SDK calls to spread through widgets or treating login as authorization. Keep Firebase at the edge:
presentation -> use cases -> repositories -> Firebase adapters
Create separate projects for development, staging, and production. Client configuration identifies the project; administrative credentials and rules still require protection.
Authentication and session
abstract interface class AuthRepository {
Stream<AppUser?> authStateChanges();
Future<AppUser> signInWithEmail(String email, String password);
Future<void> signOut();
}
The implementation translates FirebaseAuthException into application failures. Login messages should not disclose account existence when that creates an enumeration risk.
Authentication establishes identity. Authorization depends on backend rules, claims, and validated data. Hiding a button does not protect a resource.
Firestore modeling
Design collections around actual read patterns. Firestore is not a generic relational database; controlled duplication can be appropriate when consistency ownership is explicit.
final snapshot = await firestore.collection('products').limit(50).get();
final products = snapshot.docs
.map((doc) => ProductDto.fromJson({'id': doc.id, ...doc.data()}))
.map((dto) => dto.toEntity())
.toList(growable: false);
Plan pagination and indexes, and keep listeners active only while needed. Cost is an architectural requirement too.
Security Rules are production code
Deny by default and grant by identity, role, and ownership. Validate writable fields and types. Test rules in the Emulator Suite.
match /products/{productId} {
allow read: if request.auth != null;
allow write: if request.auth != null
&& request.auth.token.role == 'admin';
}
Adapt the rule to the real model and never trust a role supplied by the client.
Safe file uploads
Validate size, allowed type, and path. Generate collision-resistant names and keep only the necessary reference in the domain. Expose progress, cancellation, and interrupted-connection recovery.
Cloud Messaging
Ask for notification permission in context. Tokens rotate, so handle update and removal. Push payloads should not expose sensitive information on a locked screen.
Foreground delivery, background handling, and notification taps are different flows. Push-driven navigation must still validate session and destination.
Offline behavior
Firestore’s offline cache improves continuity but does not solve every business conflict. Indicate unconfirmed writes and test reconnection, revoked permission, and rejected synchronization.
Guided practice: integrate Firebase without moving trust to the client
You will continue Sales Management. Start only when the application still runs with the fake adapters from Module 3: this gives you a safe fallback while Firebase is connected at the infrastructure boundary.
Checkpoint 1 — create an isolated development project
Install the CLIs, authenticate, and configure only a development Firebase project:
npm install -g firebase-tools
dart pub global activate flutterfire_cli
firebase login
flutterfire configure
flutter pub add firebase_core firebase_auth cloud_firestore firebase_storage firebase_messaging
Do not reuse a production project. Commit the generated client configuration only after checking the repository policy; never commit service-account files or administrative keys.
Checkpoint 2 — initialize Firebase before dependency composition
In lib/main.dart, initialize the generated options before creating adapters:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: SalesManagementApp()));
}
If your project kept Provider instead of Riverpod, preserve its root provider tree; Firebase initialization does not require changing the presentation strategy.
Checkpoint 3 — run against local emulators
Initialize and start Authentication, Firestore, and Storage emulators:
firebase init emulators
firebase emulators:start --only auth,firestore,storage
Connect in development only. Android emulators reach the host through 10.0.2.2; desktop and web normally use localhost:
const host = String.fromEnvironment(
'EMULATOR_HOST',
defaultValue: 'localhost',
);
await FirebaseAuth.instance.useAuthEmulator(host, 9099);
FirebaseFirestore.instance.useFirestoreEmulator(host, 8080);
await FirebaseStorage.instance.useStorageEmulator(host, 9199);
Run Android with --dart-define=EMULATOR_HOST=10.0.2.2. The checkpoint is complete when the Emulator Suite UI shows the app connection and no production data is touched.
Checkpoint 4 — implement the authentication adapter
Create lib/features/auth/data/firebase_auth_repository.dart and translate SDK errors at the boundary:
final class FirebaseAuthRepository implements AuthRepository {
FirebaseAuthRepository(this._auth);
final FirebaseAuth _auth;
@override
Stream<AppUser?> authStateChanges() => _auth.authStateChanges().map(
(user) => user == null ? null : AppUser(id: user.uid),
);
@override
Future<AppUser> signInWithEmail(String email, String password) async {
try {
final result = await _auth.signInWithEmailAndPassword(
email: email.trim(),
password: password,
);
return AppUser(id: result.user!.uid);
} on FirebaseAuthException catch (error) {
throw AuthenticationFailure.fromCode(error.code);
}
}
@override
Future<void> signOut() => _auth.signOut();
}
The UI receives application failures, not Firebase exception types or messages that reveal whether an account exists.
Checkpoint 5 — deny by default and prove authorization
Create firestore.rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /products/{productId} {
allow read: if request.auth != null;
allow create, update, delete: if request.auth != null
&& request.auth.token.role == 'admin';
}
}
}
In the emulator, prove four cases: anonymous read is denied; authenticated read succeeds; a regular user cannot write; an admin can write a valid product. A hidden edit button is useful feedback, but the rule is the security control.
Checkpoint 6 — add files and messaging deliberately
Implement Storage upload behind a repository, exposing progress and cancellation. Register messaging tokens only after consent, handle rotation, and never log the token or place sensitive content in push payloads.
Common failures:
| Symptom | Likely cause | Check |
|---|---|---|
| Android cannot reach an emulator | localhost points to the device | use 10.0.2.2 |
permission-denied for an admin | role claim is absent or stale | inspect the emulator user and refresh sign-in |
| app uses production accidentally | emulator connection ran too late | connect before repository composition |
| test passes only through the UI | rules were not exercised directly | add allowed and forbidden emulator cases |
Completion gate and independent practice
Run flutter analyze, flutter test, and the four authorization scenarios. Restart the app and confirm that auth state is restored and sign-out returns to the login screen.
Then add an orders collection independently. Define ownership rules so a user can read only their orders, write emulator tests for both the owner and a different user, and explain in the README why client-side filtering would not provide the same protection.
Deeper understanding: trust boundary
Firebase reduces infrastructure operated by the team; it does not remove authorization modeling. The client is outside the trust boundary: submitted fields, displayed roles, and paths built in the app can be modified. Security Rules and trusted functions must validate identity, ownership, transition, and shape.
Model rules together with queries. A query that reads an entire collection and filters on the client is poor for security, cost, and performance. Design indexes and document structure around authorized access patterns.
Use Emulator Suite for reproducible, isolated tests. A test should attempt both allowed and forbidden paths because proving only success does not validate authorization. Production data and credentials do not belong in the lab.
Terms to review: authentication, authorization, trust boundary, Security Rules, claim, emulator, document, and index. See the course reference.