When I started working on an RBAC implementation, the goal looked simple: build one central point to control who could access each resource across the applications.
The first idea: an RBAC Core
The first idea was to build an RBAC Core. Every application would query this service before allowing an action. The fixed diagram keeps the topology visible while the notes beside it explain where that centralization starts to weigh on the system.
Architecture diagram
The centralized path
Every decision passes through RBAC Core.
Every app calls one point
Each request reaches RBAC Core before an action is allowed.
Core coordinates every decision
Core queries OpenFGA and returns allow or deny to the application.
Concentration creates dependency
Availability and business rules from every system accumulate in one service.
In theory, this would deliver standardization, governance, and a single interface. In practice, some important questions started surfacing.
What would happen if the RBAC Core went down? Would every application lose the ability to authorize users? Who would be responsible for updating the authorization models? Would the team owning the central service need to understand every business rule of every system?
Initial decision
Decision
Centralize authorization in an RBAC Core.
Rationale
Standardize the interface and governance across applications.
Consequence
The central service sits on the path of every authorization decision.
I realized we weren’t just building a central service — we were building a possible single point of failure and an organizational bottleneck.
The turn: a federated model
The architecture then started evolving toward a federated model. The scenario separates runtime decisions from central governance.
Architecture diagram
The federated path
Local ownership; governance outside requests.
Each app owns its model
Relations and policies live near the domain they protect.
SDK talks directly to OpenFGA
The critical path removes the intermediary Core and supports short decision caching.
Centralize governance, not runtime
Versioned models, audit, and visualization remain central but outside requests.
Each application became responsible for its own authorization model, its own relations, and its own policies. Those policies would be treated much like database migrations: versioned, reviewed, and applied idempotently during deploy.
The central service stopped sitting in the critical path of requests. Its role shifted to governance: visualizing models, tracking changes, running audits, and helping administer permissions.
We also understood that authorization shouldn’t depend entirely on the control plane’s availability. That’s why strategies like short-lived caching of decisions, automated model application, and direct communication between each system and OpenFGA entered the discussion.
Centralize or federate
What stuck
The main discovery from this journey was that RBAC isn’t just a table of users, roles, and permissions. It’s a distributed architecture decision.
Centralizing governance can be valuable. Centralizing every runtime decision, though, can increase coupling and reduce resilience.
In the end, the implementation stopped being just about controlling access. It became about finding the balance between autonomy, security, governance, and availability.
And maybe that was the biggest lesson: good authorization architecture isn’t the one that concentrates all control, but the one that distributes responsibility without losing the ability to govern the whole.