zanzibar is an open-source authorization library designed for building enterprise-grade, permissions-aware applications. Instead of hardcoding roles (admin, user) into your application logic, Zanzibar allows you to define flexible relationships between resources and subjects, and recursively resolves permissions at runtime.
Built on sqlx and PostgreSQL recursive Common Table Expressions (CTEs), this engine effortlessly scales to millions of relational tuples while maintaining millisecond-level resolution times.
🚀 Features
- Pure ReBAC: Implement Google Drive, GitHub, or AWS IAM style permissions instantly.
- Computed & Inherited Relations: Model complex inheritance (e.g.
Folderviewers automatically getDocumentviewer access). - Postgres Native: Leverages highly-optimized recursive CTEs. No graph database required.
- Stateless & Async: Built on
tokioandsqlx. Drop it into any async Rust web framework (Axum, Actix, etc). - Mathematical Correctness: Hardened via
proptestgraph fuzzing to guarantee loop safety and transitive consistency.
📦 Installation
Add zanzibar to your Cargo.toml:
[]
= "0.1.0"
= { = "0.7", = ["postgres", "runtime-tokio-rustls"] }
Applying the Schema
The crate exports its required Postgres tables as a string constant so you can easily include it in your application's migration runner.
use PgPool;
async
🧠 Core Concepts
Zanzibar uses four primary concepts to determine access:
- Object: The resource being accessed (e.g.,
document:readme.md). - Subject: The entity attempting access. Can be a specific user (
user:alice) or a group of users (group:engineering#member). - Relation: The type of connection between the Object and Subject (e.g.,
viewer,owner). - Tuple: A directed edge in the database establishing a fact: Subject has Relation to Object.
🛠️ Usage & Examples
1. Simple Scenario: Direct Access
The most basic usage is granting a user a direct relation to a specific resource.
use ;
// 1. Initialize the engine
let engine = new;
let tenant_id = 1;
let doc = Object ;
let alice = Entity;
// 2. Write the tuple: Alice is a viewer of doc:1
engine.write_tuples.await?;
// 3. Check access
let has_access = engine.check.await?;
assert!; // true
2. Intermediate Scenario: Group Memberships (Usersets)
Instead of adding every user to a document, add a group to the document, and add users to the group.
let engineering_group = Object ;
// The Engineering group's members are viewers of doc:1
engine.write_tuples.await?;
// Alice is a member of the Engineering group
engine.write_tuples.await?;
// Alice is now transitively a viewer of doc:1
let has_access = engine.check.await?;
assert!; // true
3. Advanced Scenario: Google Drive (Recommended Typed API)
When building large systems, constructing Object and Subject structs manually using raw strings is error-prone. We strongly recommend wrapping the zanzibar engine in a strictly typed, domain-specific API.
Here is how you would implement a Google Drive clone where Documents inherit permissions from their parent Folders, using a strictly typed wrapper.
Step 1: Define the Schema
First, register the relational algebra with the engine so it knows how relationships cascade.
use ;
use HashMap;
let schema = new
.namespace
.namespace
.build;
engine.apply_schema.await?;
Step 2: Build the Typed Wrapper
Step 3: Use the Wrapper
Your business logic is now incredibly clean, secure, and type-safe.
let auth = DriveAuth ;
// 1. Add doc_1 inside folder_X
auth.add_doc_to_folder.await?;
// 2. Make Alice a viewer of folder_X
auth.add_folder_viewer.await?;
// 3. Alice can now view doc_1 because it is in folder_X!
let can_view = auth.can_view_doc.await?;
assert!; // true
📈 Performance & Scaling
zanzibar includes a high-performance benchmarking suite. Using a local PostgreSQL 16 instance, the engine yields the following p50 latencies:
| Scenario | Tuple Count | Latency (P50) | Description |
|---|---|---|---|
| Micro: Width | 1,000 | 0.6ms |
Direct hit against a document with 1,000 direct viewers. |
| Micro: Depth | 50 Levels | 1.8ms |
Traversal check against 50 deeply nested parent folders. |
| Enterprise: Load | 10,000,000 | 331ms |
100 concurrent workers hammering the DB simultaneously with exhaustive miss queries on 10 million tuples. |
Enterprise Scaling Roadmap
Relying entirely on PostgreSQL Recursive CTEs works phenomenally up to around ~10 Million rows, at which point the memory overhead of the intermediate join sets (work_mem) begins to cause latency spikes.
For future releases, zanzibar plans to incorporate:
- Application-Layer Traversal: Moving graph compute out of Postgres and into asynchronous Rust
tokiotasks for infinite horizontal scalability. - Reachability Caching: Integrating Bloom Filters to instantly reject negative lookups without hitting the database.
🤝 Contributing
We welcome contributions! Please ensure you run the proptest graph fuzzer and the criterion benchmarks before submitting a PR.
# Run property-based correctness fuzzer
# Run micro-benchmarks
# Run the 10-Million row stress test