TL;DR
Hard tenant isolation on Firestore is not a query-pattern choice — it is a middleware layer, an immutable claim source, and a permission model with wildcards.
Hard multi-tenancy on Firestore — where tenant A literally cannot see tenant B's data — is not a query-pattern. You cannot "remember to filter by org_id" your way to security. It has to be a middleware layer, an immutable claim source, and a permission model that expands cleanly without granting more than intended. The architecture below is what shipped.
TenantContextMiddleware
Every API request hits a middleware that does three things: reads the immutable Firebase custom claim, looks up organizations/{orgId}/permissions in Firestore, and attaches a request-scoped context to the request object. Every downstream operation reads from request.context — never from the raw token, never from the URL, never from a header the client controls.
- Custom claim: set at signup by an admin SDK action; immutable until rotated
- Permissions doc: editable by org owners; read-only at request time
- Context: {orgId, userId, roles[], permissions[]} — all derived, none client-supplied
Firestore queries: scoped at the path layer
Every collection lives under organizations/{orgId}/. There is no top-level documents collection — only organizations/{orgId}/documents. A query that omits the orgId path segment fails at dispatch. The system has no way to "accidentally" query across tenants because the path itself enforces scope.
Permissions: 60+ wildcard-driven
Permission strings: "{resource}:{action}:{scope}". Examples: "documents:read:org" (read documents in own org), "documents:*:org" (any action on documents), "billing:read:platform" (read billing across platform — admin-only). 5 platform/org roles map to permission sets; assignment is per-user.
- Wildcard expansion at permission-check time, not at storage. "documents:*:org" expands to read+write+delete+list when checked.
- Scope is the third segment — "org" stays in the user's tenant; "platform" cross-tenant; "self" only the user's own resources.
- 60+ raw permissions; 5 roles bundle them; assignment is per-user with override support.
Secrets: backend-only, never serialized
Tenant secrets (API keys, tokens, OAuth refresh tokens) live in a backend-only collection. Firestore security rules deny all client reads. Only the backend service account can read them, only at the moment of use. Secrets never travel to the frontend, never appear in any response payload, never get logged.
What can still go wrong
- A developer forgets the middleware on a new endpoint — the query fails because of path scoping, but error messages might leak existence info. Mitigation: 401 on auth failure, generic 404 on missing resource, never 403.
- A client manipulates a URL parameter to access another org's resource — middleware reads the immutable claim, ignores the URL, scopes the query correctly.
- A wildcard expansion grants more than intended — quarterly permission audit + a test suite that asserts no wildcard expands to a cross-scope grant.
- Custom claim rotation gap — there is a 1-hour window where the old claim is still valid. Mitigation: rotate-and-revoke is a two-step process documented in runbooks.
Numbers from production
- 60+ permissions, 5 roles, 15 Firestore composite indexes
- 7 alert policies (latency, error rate, auth failures, permission denials, cross-tenant attempts)
- 8 Cloud Scheduler jobs for periodic audits and maintenance
- P95 endpoint latency: 5s alert; permission-check latency: 5-10ms (cached)
What this architecture is and is not
It is hard tenant isolation, defense in depth, and a permission model that survives growth. It is not a complete zero-trust architecture (which also requires mTLS between services, secrets management beyond Firestore, audit logging, and incident-response runbooks). The architecture above is the part that lives in the application code; the rest belongs in infrastructure.
Share this article
Muhammad Mudassir
Founder & CEO, Cognilium AI | 10+ years
Muhammad Mudassir
Founder & CEO, Cognilium AI | 10+ years experience
Mudassir Marwat is the Founder & CEO of Cognilium AI. He has shipped 100+ production AI systems acro...
