From RBAC to ReBAC: when and how to migrate
Migrate from RBAC to ReBAC when access depends on which object a user is related to, not just what role they hold. If you are creating roles like editor_for_project_xyz, modeling "share this folder with Bob," or encoding org and team trees in role names, you have outgrown RBAC. Relationship-based access control stores those relationships as data and computes permissions by walking them. The migration is low risk if you run both systems side by side and cut over one call site at a time.
This guide is for engineers who own authorization in a multi-tenant SaaS product or internal platform. For the concepts first, see RBAC vs ABAC vs ReBAC.
Recognize the signs
- Role explosion: more roles than active users, or roles that encode a resource ID.
- Per-object sharing: "share this with Bob but not Alice" cannot be expressed without a new role.
- Inheritance: nested folders, projects inside workspaces, organization and team hierarchies.
- Tenant-defined access: each customer wants its own groups and sharing rules.
- Listing is slow: "show me every document I can see" means loading everything and filtering in application code.
Choose the model: RBAC, ABAC or ReBAC
| Question you need to answer | Best fit | Why |
|---|---|---|
| "Can admins manage billing?" | RBAC | Coarse function, few roles, rarely changes |
| "Can this user edit doc 42 because they own its parent folder?" | ReBAC | Permission flows through relationships |
| "Allow only from a managed device during business hours" | ABAC | Depends on request attributes, not stored relationships |
| "Who can see this record?" (reverse lookup) | ReBAC | Graph engines answer list queries natively |
| "Owner can delete if the record is not under legal hold" | ReBAC plus conditions, or a policy engine | Relationship plus an attribute check |
Most real systems end up hybrid. The choice is which concern is the hard part: the graph (pick a Zanzibar-style engine) or the attribute logic (pick a policy engine).
Where ReBAC comes from
The dominant design is Google's Zanzibar, described in the USENIX ATC 2019 paper. Zanzibar stores access control lists as relation tuples and, per the paper, serves "trillions of access control lists and millions of authorization requests per second" for hundreds of Google services, with 95th-percentile latency under 10 milliseconds and availability above 99.999% over three years. You will not need that scale, but the data model and the consistency approach (the paper's "zookies") are what the open engines copy. The broader category is often called fine-grained authorization.
The engines
| Engine | Model | Where relationship data lives | Notes |
|---|---|---|---|
| OpenFGA | Zanzibar-style | In the engine (tuples) | CNCF incubating project; managed option via Auth0 FGA |
| SpiceDB (AuthZed) | Zanzibar-style | In the engine | Explicit per-request consistency with ZedTokens |
| Cerbos | Policy engine (PDP) | Your systems, passed per request | Stateless and AuthZEN-compliant |
| Oso | Polar policy language | Mixed | Evaluate as a peer option |
| Permit.io | RBAC, ABAC and ReBAC in one control plane | Managed | Useful when non-engineers edit policy |
| Permify | Zanzibar-style, open source | In the engine | Simpler to operate |
Cerbos shows the other path: its derived roles are "computed at request evaluation time from the principal's base roles and request attributes," for example an owner role when request.resource.attr.owner == request.principal.id. That handles ownership without a separate relationship store, but deep inheritance chains are awkward because you must pass the chain in.
Whichever engine you pick, put a thin interface between your application and the decision call. The AuthZEN Authorization API 1.0 (spec), the OpenID Foundation's standard API between enforcement and decision points, is the obvious shape for it and keeps a later engine swap from touching every call site.
A worked model
An OpenFGA schema 1.1 model for folders and documents, using the union (or), inheritance (from) and exclusion (but not) operators described in the configuration language docs:
model
schema 1.1
type user
type folder
relations
define owner: [user]
define editor: [user] or owner
define viewer: [user] or editor
type document
relations
define parent: [folder]
define owner: [user]
define editor: [user] or owner
define blocked: [user]
define viewer: ([user] or editor or viewer from parent) but not blocked
Data is written as tuples of user, relation and object, as in the OpenFGA modeling guide: { user: "user:anne", relation: "owner", object: "document:42" }, { user: "folder:legal", relation: "parent", object: "document:42" }, or a group grant such as { user: "organization:A#member", relation: "editor", object: "document:1" }. Your old role editor_for_project_xyz becomes one tuple per member instead of one role per project.
The migration, step by step
- Inventory call sites. List every place that checks a role. Each becomes a
check(user, relation, object)call. This list is your cutover plan. - Model first. Write the schema and unit-test it against the engine's test tooling before touching production code. Design deny rules (
but not blocked) now; they are hard to add later. - Backfill. Translate existing role assignments into tuples. Map each role to the smallest relation that reproduces it, and log any role you cannot map; those are your design gaps.
- Dual-write. Every grant, revoke, share and membership change writes to RBAC and to the engine. Use an outbox or change-data-capture so a failed engine write is retried, not dropped.
- Shadow reads. Keep RBAC authoritative, call the engine in parallel, and log every divergence with the user, object and both answers. Divergence is either a model bug or a backfill bug; fix and re-run.
- Cut over per call site. Flip one endpoint at a time behind a flag, starting with read paths. Rollback is flipping the flag back.
- Decommission RBAC. Only after shadow reads have agreed long enough to cover rare paths (month-end jobs, admin consoles, exports). Keep coarse admin roles as relations in the model.
Consistency and caching
A permission check against stale data is how a removed user still sees a document. SpiceDB's docs call this the New Enemy Problem, and the engines handle it differently:
- SpiceDB lets each request choose
minimize_latency,at_least_as_fresh(with a ZedToken),at_exact_snapshotorfully_consistent. Per the consistency docs, writes default to fully consistent and other APIs default tominimize_latency; store the ZedToken returned by a write alongside the resource and pass it on later checks. - OpenFGA offers
MINIMIZE_LATENCY(default) andHIGHER_CONSISTENCY, which skips the cache. Per the docs, caching is disabled by default, so checks are strongly consistent until you turn it on.
Rule of thumb: revocations and sharing changes on sensitive objects use the stronger mode; bulk list views use the faster one.
Common failure modes
| Failure | What it looks like | Prevention |
|---|---|---|
| Missed call site | One endpoint still uses the old check, or none at all | Inventory first; fail closed when no check runs. The Optus API exposure shows the cost of one endpoint without working access control |
| Carried-over over-privilege | Backfill copies every stale role into tuples | Run an access review before backfill; see mover entitlement accumulation |
| Stale decisions | Revoked user retains access through the cache | Consistency tokens or the high-consistency mode on revocation paths |
| ReBAC as a library | Relationships duplicated in app tables and the engine drift apart | Treat the engine as the system of record for relationships |
| Latency surprise | Every request now makes a network hop | Batch checks, co-locate the engine, budget latency per endpoint |
| No deny rules | Cannot express "everyone in the org except contractors" | Model exclusion from day one |
Related
Guides: implementing RBAC in the enterprise, implementing ABAC. Glossary: ReBAC, fine-grained authorization, AuthZEN, least privilege. Vendors: authorization.
Frequently asked questions
- When should I move from RBAC to ReBAC?
- Move when access depends on the relationship between a user and a specific object rather than on a job function: per-document sharing, nested folders or projects, team and organization hierarchies, or tenants that define their own access. The common symptom is role explosion, where you keep minting roles scoped to a single resource.
- Is ReBAC a replacement for RBAC?
- No. Roles are themselves relationships (a user is a member of a role, a role is assigned on an object), so a ReBAC model can express RBAC. Most teams keep coarse roles for administrative functions and use relationships for resource-level sharing and inheritance.
- What is the difference between OpenFGA, SpiceDB and Cerbos?
- OpenFGA and SpiceDB are Zanzibar-style engines: they store relationship tuples and answer permission checks by walking the relationship graph. Cerbos is a stateless policy decision point that evaluates policies against attributes passed in each request, so the relationship data lives in your systems, not in Cerbos. Pick a Zanzibar engine when the graph is the hard part; pick a policy engine when attributes and conditions are.
- How do I avoid stale permissions after someone loses access?
- Use the engine's consistency controls. SpiceDB returns ZedTokens you can store with a resource and pass back to require data at least as fresh as the last change. OpenFGA offers a HIGHER_CONSISTENCY option that skips its cache. Revocations and access changes on sensitive objects should use the stronger mode.
- How long does an RBAC to ReBAC migration take?
- It depends on how many call sites check permissions and how messy the existing role data is. The safe pattern (model, backfill, dual-write, shadow reads, cutover per call site) is designed so each step is reversible, and the decommission step should wait until shadow reads have agreed with RBAC for long enough to cover your rare paths, such as month-end jobs and admin tools.
Related on Start with Identity
- RecipeMigrate an app from AD FS to Microsoft Entra ID
Move a single SAML relying party trust from AD FS to a Microsoft Entra enterprise application: export the trust with Get-AdfsRelyingPartyTrust, map the identifi
- RecipeMigrate users from Auth0 to another CIAM without forcing password resets
Export Auth0 users with the Management API, request the bcrypt password hashes through an Auth0 support ticket (not on the Free plan), then import the hashes in
- VendorAserto
strong
- Comparisonauthzed-vs-openfga
Both are Apache 2.0 Zanzibar-style ReBAC engines. SpiceDB (AuthZed) has token-based point-in-time consistency and capacity-priced managed tiers; OpenFGA is a CN
- VendorAWS Verified Permissions
strong_contender
- ComparisonOpenFGA vs AuthZed (SpiceDB) vs Cerbos in 2026
OpenFGA vs AuthZed SpiceDB vs Cerbos: two Zanzibar ReBAC engines and one stateless policy engine, with 2026 CNCF status and managed pricing from $2 an hour.