FaunaDB Multi-Tenant Authorization: Roles vs Application-Side Checks
Choose between FaunaDB built-in ABAC Roles and application-side checks for multi-tenant serverless apps. Compare atomic enforcement, shared collections and maintenance cost, with a role predicate pattern and verification steps.
12 Feb 2026, 15:05 UTC

The decision you actually need to make
A serverless multi-tenant app using FaunaDB document collections has to decide where tenant isolation is enforced. The practical choice is FaunaDB built-in attribute-based access control via Roles, or application-side checks in your API layer. If the data lives in shared collections, Roles let the database enforce tenant boundaries inside the same transaction as the read or write. Application checks move that decision to your code and require a separate trust boundary.
ABAC in FaunaDB means privileges are granted per collection, index, function, key or token, and a resource lambda can inspect the document, the request identity and the token to allow or deny an individual operation. An admin or server key bypasses Roles entirely.
Options at a glance
| Approach | Where enforcement lives | Atomicity | Multi-tenant sharing | Operational cost |
|---|---|---|---|---|
| FaunaDB Roles with resource lambdas | Database, evaluated per operation | Decision and data change in one transaction | Shared collections with tenant field predicates | Role FQL to maintain alongside schema |
| Application-side checks | API / serverless function | Check then query, risk of time-of-check/time-of-use | Requires app to filter and validate every query | Simpler DB, more app logic and audit surface |
| Child databases per tenant + keys | Database isolation | Full isolation per DB | No shared collections | Key and DB sprawl, harder cross-tenant ops |
Trade-offs that matter for serverless
Roles move the deny decision into FaunaDB. A checked write and its permission decision are evaluated together, so a tenant cannot change a document it is not allowed to see even under race conditions. The predicate is written in FQL and must be maintained with the collection schema. Adding or renaming a tenant field can silently change predicate behavior until the Role is updated.
Application checks keep authorization in your code. That is familiar, but you must ensure every read and write path applies the tenant filter and that no admin key is reachable from clients. A leaked server key is equivalent to full database compromise because it bypasses Roles.
Child databases provide strong isolation but increase operational overhead for provisioning, monitoring and cross-tenant queries. Role predicates are the finer-grained mechanism when tenants share collections.
Concrete role pattern for shared collections
The pattern is a Role granted to a scoped key or auth token, with a resource lambda that compares a document tenant field to the token identity.
Run this in Fauna Shell or a backend client using an admin key. Admin keys are required to create Roles and must never be embedded in clients or mobile apps.
// Create a test collection and sample docs
CreateCollection({ name: "items" })
// Example documents would include a tenant field, e.g. tenant_id
// Role skeleton, syntax is FQL version sensitive
CreateRole({
name: "tenant_reader",
privileges: [{
resource: Collection("items"),
actions: { read: true },
// resource lambda inspects document and identity
// predicate example: document tenant matches token identity
// exact lambda shape depends on your FQL version and identity model
}]
})
Create two tokens with different identities, e.g. tenant_a and tenant_b, and bind them to the Role. Use the matching token to read a document where tenant_id equals the identity, and use the non-matching token to attempt the same read. The expectation is success for the matching identity and an authorization failure for the non-matching identity.
Attempt a write or delete with the non-matching token and confirm it is rejected. Repeat the checks with an admin key only in a backend test to confirm the admin key bypasses Roles.
Limitations and how to verify
Role predicates are FQL and version sensitive. New databases may default to a newer FQL, so privilege resources and lambda syntax should be validated against the target database's FQL version and region.
A misconfigured resource lambda can return true for all documents and over-expose tenant data. Test deny cases explicitly, not only happy paths.
Service status, pricing and feature availability can change. Confirm current support before relying on this design.
Practical verification: create a test collection with two documents for different tenants, create the Role with a tenant-matching predicate, issue two tokens with different identities, and run permitted and denied reads and writes. Repeat the checks in the target region and FQL version because behavior can differ.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.