Chapter 04 · CRUD with Client and Server SDKs: Reads, Writes, Updates, Deletes, Preconditions, and Converters
Server vs Client SDK Authentication / Authorization Models and Trusted vs Untrusted Execution
Separate untrusted mobile/web requests enforced by Firebase Authentication and Security Rules from trusted server/Admin requests governed by IAM and application authorization, then prove the difference in the emulator.
Learning outcomes
AtlasMart exposes product/profile functionality to browsers while also running trusted backend jobs. Both can call Firestore, but they do not pass through the same authorization mechanism. Treating Admin SDK code as “the same Firestore client with more methods” is a security design error: mobile/web requests are evaluated by Firestore Security Rules, whereas Admin/server libraries use privileged credentials/IAM and bypass those Rules.
Trace a Web SDK request through Firebase Authentication and Firestore Security Rules.
Trace an Admin/server request through the privileged credential/IAM path and explain why Rules do not protect it.
Separate authentication, authorization, validation, and app attestation rather than treating them as synonyms.
Prove rule denial and server bypass behavior locally without embedding service-account keys in client code.
Design a trusted backend so privileged database access is narrower than “any server request may write anything.”
The lab continues Chapters 01–03 without changing the
environment: project demo-atlasmart-firestore;
Firestore emulator 127.0.0.1:8080; Auth emulator
127.0.0.1:9099; Emulator UI
127.0.0.1:4000; Firebase CLI
15.30.0; Firebase JavaScript SDK
12.19.0; Firebase Admin Node.js SDK
14.4.0 (which currently carries
@google-cloud/firestore 9.1.0); Node.js 22 or
newer. The default teaching surface is Firestore Standard
edition, Native mode, Core operations, default database
(default), emulator-first.
Enterprise/Pipeline/MongoDB-compatibility behavior is labeled
rather than generalized.
Browser/mobile SDK requests are untrusted requests evaluated by Firebase Authentication context plus Cloud Firestore Security Rules. Admin/server client libraries are privileged and bypass Firestore Security Rules; production access is governed by IAM/ADC and by authorization logic in your server application. The emulator demonstrates these trust paths and CRUD semantics, but it does not prove production IAM, regional latency, billing, quotas, contention, or service availability. Expected failures are described by invariant/error family rather than fabricated captured output.
1. The same document path can have two different trust paths
A browser using the Firebase Web SDK is an untrusted client. Firebase Authentication can establish who the user is; Security Rules decide whether that authenticated request may read/write the target documents and can validate incoming fields. App Check can help attest that requests originate from an authorized app instance, but it is not user authorization. A trusted Node backend using the Admin SDK or a Google Cloud server client authenticates with privileged credentials and IAM; Firestore Security Rules are bypassed.
| Question | Web/mobile path | Trusted server/Admin path |
|---|---|---|
| Who is the caller? | Firebase Auth user token (when signed in) | Service/workload identity via ADC/IAM plus application caller identity |
| Who authorizes Firestore request? | Security Rules | IAM authorizes database access; server code must enforce business authorization |
| Do Firestore Rules run? | Yes | No |
| Can privileged credentials be shipped to browser? | No | Keep only in trusted environment; prefer ADC/keyless workload identity where supported |
| Can the server trust client-supplied role/owner fields? | No | No; server must derive/verify authorization context |
2. Minimal client Rules: protect ownership and server-managed fields
Chapter 12 will develop Security Rules systematically. For CRUD
today, use a narrow rule sufficient to prove the trust boundary.
AtlasMart lets a signed-in user read and update their own
profile but prevents the client from changing
isStaff or schemaVersion. The backend
may migrate those fields because it is a privileged path—but
only after its own caller authorization and input validation.
rules_version = "2";service cloud.firestore { match /databases/{database}/documents { match /profiles/{uid} { allow read: if request.auth != null && request.auth.uid == uid; allow create: if request.auth != null && request.auth.uid == uid && request.resource.data.keys().hasOnly(["displayName", "locale", "preferences", "schemaVersion"]) && request.resource.data.schemaVersion == 2; allow update: if request.auth != null && request.auth.uid == uid && request.resource.data.diff(resource.data).affectedKeys() .hasOnly(["displayName", "locale", "preferences"]); allow delete: if request.auth != null && request.auth.uid == uid; } }}
This is intentionally a Chapter 04 teaching rule, not a finished production policy. Later security chapters add type/length validation, query compatibility, claims, App Check, helper functions, adversarial tests, access-call accounting, and incident considerations.
3. Prove client denial through the emulator
Continue the same local AtlasMart workspace. Keep
firebase emulators:start --only auth,firestore
running. Admin/server examples set
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 and
GCLOUD_PROJECT=demo-atlasmart-firestore; browser
examples connect the modular Web SDK explicitly to the
Firestore/Auth emulators. Use only synthetic Chapter 04
documents such as profiles/u-alice,
products/p-1001,
crudOperations/<operationId>, and named test
users. Cleanup must target only those paths.
import { doc, updateDoc } from "firebase/firestore";const mine = doc(db, "profiles", currentUser.uid);await updateDoc(mine, { locale: "fa" }); // expected allowed when Rules matchawait updateDoc(mine, { isStaff: true }); // expected denied by Rulesawait updateDoc(doc(db, "profiles", "u-bob"), { locale: "fa" }); // expected denied
Record the identity (uid), path, attempted fields,
and allow/deny outcome. A denial proves the tested request was
rejected under the active emulator rules; it does not prove
every production rule path is correct. Use Rules unit tests for
repeatable matrices instead of relying only on manual UI
behavior.
4. Prove that Admin/server code bypasses those Rules
Point the Admin SDK at the same emulator and update
isStaff. The privileged write succeeds because
Firestore Security Rules are not evaluated for server client
libraries. This is not a flaw; it is the intended separation.
The security boundary moves into IAM and your server's own
authorization logic.
import { initializeApp } from "firebase-admin/app";import { getFirestore, FieldValue } from "firebase-admin/firestore";initializeApp({ projectId: "demo-atlasmart-firestore" });const db = getFirestore();async function setStaffFlag({ actor, targetUid, enabled, correlationId }) { if (!actor?.roles?.includes("admin")) { throw Object.assign(new Error("forbidden"), { code: "APP_FORBIDDEN" }); } const ref = db.doc(`profiles/${targetUid}`); const result = await ref.update({ isStaff: enabled, serverUpdatedAt: FieldValue.serverTimestamp() }); console.log(JSON.stringify({ correlationId, path: ref.path, updateTime: result.updateTime.toDate().toISOString() }));}
The important line is the application authorization gate, not
the database method. In production, actor must come
from a verified request/session/token or trusted job identity,
never from an arbitrary JSON body saying
{"role":"admin"}.
5. IAM answers “may this workload access Firestore,” not every product rule
IAM should grant the backend only the database permissions its
workload needs. Even a correctly scoped Firestore IAM role
generally does not know that “seller A may edit only products
owned by seller A” unless your architecture maps that product
rule into separate identities/resources. Most product
authorization still belongs in trusted application code for the
server path. Use structured authorization functions and tests,
not scattered if statements.
Do not commit service-account private keys, embed them in HTML/JavaScript, or paste them into course fixtures. On Google-hosted environments prefer Application Default Credentials/workload identity. The emulator path in this chapter needs no production secret.
6. Failure taxonomy: rule denial is not IAM denial
| Failure | Typical boundary | Evidence to log | Repair direction |
|---|---|---|---|
| Client permission denied | Security Rules | uid, path, operation, rule test case—not secrets | Fix Rules/query/data contract or deny intentionally |
| Server IAM permission denied | Workload identity / IAM | service identity, resource, operation, correlation ID | Least-privilege IAM/configuration |
| Server application forbidden | Business authorization | verified actor ID/role, target ownership, policy decision | Product authorization policy |
| Validation failure | Rules or server validator | field/path/schema error, safe reason code | Fix payload/client/schema |
7. Deliberately wrong approach: “Admin SDK is protected by my Firestore Rules”
A backend accepts arbitrary path and
data from the browser, then calls Admin SDK
set(), assuming Firestore Rules will block
forbidden fields. They will not. The backend has converted an
untrusted request into a privileged database mutation and
potentially bypassed the client security model. Repair it by
defining server-owned endpoints/use cases, authenticating the
caller, authorizing the business action, validating a narrow
schema, deriving server-managed fields, and using
least-privilege workload credentials.
Knowledge check
Check your understanding
- Which mechanism authorizes a normal Firestore Web SDK request?
- Do Firestore Security Rules protect writes performed by Admin/server client libraries?
- Why is App Check not a replacement for user authorization?
- What must a privileged backend do before writing user-requested changes?
- Why should rule denial and IAM denial be logged as different failure classes?
Review the answers
1. Firebase Authentication can establish user identity, while Firestore Security Rules evaluate whether the mobile/web request is allowed and valid.
2. No. Server client libraries bypass Firestore Security Rules and authenticate/authorize with IAM/ADC; business authorization remains the application's responsibility.
3. App Check attests the calling app/device context to reduce abuse; it does not decide which authenticated user owns a document or may perform a business action.
4. Verify the caller, authorize the specific use case, validate/normalize the payload, derive privileged fields server-side, then perform the database mutation.
5. They occur at different trust layers and have different causes/remediation. Collapsing them into “permission error” makes diagnosis and security review weaker.
Summary and next step
Client and server SDKs can touch the same Firestore path while crossing different trust boundaries. Browser/mobile requests depend on Auth + Rules; server/Admin code bypasses Rules and therefore must combine IAM with explicit application authorization/validation. Next, encode the data contract itself so typed models do not become a false sense of schema safety.
Next: Data Converters/Typed Models, Serialization, Validation, and Backward-Compatible Schema Evolution.
Authoritative references
- Add data to Cloud Firestore — Official set, merge, update, nested field, server timestamp, array transform, and increment semantics.
- Get data with Cloud Firestore — Document snapshots, existence checks, and custom-object conversion.
- Delete data from Cloud Firestore — Document deletion, field deletion, collection deletion, and subcollection caveats.
- Cloud Firestore data model — Path hierarchy and the warning that deleting a document does not delete its subcollections.
- Secure data in Cloud Firestore — Mobile/web Security Rules versus server IAM trust paths.
- Security Rules conditions — Request/resource validation and explicit server-library bypass guidance.
- Test Security Rules with Emulator Suite — Deterministic local Rules testing.
- Transactions and batched writes — Retry behavior and when read-dependent writes require transactions.
- Firestore REST Precondition — Conditional existence/update-time semantics.
- Set up the Firebase Admin SDK — Privileged server setup, ADC, and current Node.js runtime requirement.
- Firebase JavaScript SDK release notes — Current Web SDK baseline.
- Firebase Admin Node.js SDK release notes — Current Admin/Firestore dependency baseline.
- Firebase CLI release notes — Current CLI baseline.