Authorization Architecture (OpenFGA)
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
The Authorization Architecture of this project is built on OpenFGA (Fine-Grained Authorization), implementing a relationship-based access control (ReBAC) model. It serves as the source of truth for determining user permissions across various resources, primarily "tables" within the CRM demo. The system is designed to allow granular control over CRUD (Create, Read, Update, Delete) actions by defining relationships between users, roles, and table resources.
The architecture decouples authorization logic from business logic, using an external OpenFGA server to evaluate tuples and relationship definitions. This allows for dynamic permission updates—such as assigning a role to a user or granting a "reader" badge to a role—without requiring code changes or complex database joins for access checks.
Sources: PLAN.md:4-15, backend/src/fga/fgaClient.ts:1-25
Relationship Model and Schema
The OpenFGA model defines the types and relations that govern access. The system utilizes three primary types: user, role, and table. Permissions are not assigned directly to users in most cases but are instead inherited via role membership.
OpenFGA DSL Structure
The model uses computed relations (can_create, can_read, etc.) to simplify permission checks. For example, a user can read a table if they are a reader, creator, updater, or deleter.
The diagram above illustrates the relationship definitions where roles contain user members, and tables define badges that can be held by either users or role members.
Sources: PLAN.md:27-44, backend/src/fga/fgaClient.ts:121-125
Backend Integration (fgaClient)
The fgaClient.ts module acts as the wrapper for the @openfga/sdk. It handles initialization, model ID resolution, and provides high-level functions for checking and writing permissions.
Core Implementation Details
- Model Management: The client automatically ensures the OpenFGA model is up-to-date by comparing the local
.fgafile with the server's models. - Permission Checking: Functions like
checkPermissionevaluate if auser:idhas a specificcan_actionon atable:slug. - Tuple Management: The system uses "badges" (creator, reader, updater, deleter) to represent direct assignments to roles.
| Function | Purpose | Input Parameters |
|---|---|---|
checkPermission | Validates user access | userId, tableSlug, action |
writeTuples | Batch writes/deletes tuples | writes[], deletes[] |
assignRoleToUser | Links user to a role | userId, roleName |
grantCrudBadgeToRole | Grants a CRUD permission | tableSlug, roleName, badge |
Sources: backend/src/fga/fgaClient.ts:89-115, backend/src/fga/fgaClient.ts:184-211
Middleware and Route Protection
Authorization is enforced at the API level using Express middleware. The authorizeTable middleware intercepts requests and queries OpenFGA before allowing access to demo modules.
Authorization Flow
When a user attempts to access a protected resource (e.g., GET /api/demo/clients), the following sequence occurs:
Permission Management (Matrix)
The system provides a Super Admin interface to manage the "Permission Matrix". This translates high-level CRUD toggles into OpenFGA tuples.
Matrix Logic
The permissions.routes.ts file manages the translation between the UI and OpenFGA tuples. When a permission is toggled, the backend identifies the corresponding "badge" (e.g., create maps to creator) and updates the tuple in OpenFGA.
const relationMap = {
create: "creator",
read: "reader",
update: "updater",
delete: "deleter"
} as const;
Sources: backend/src/modules/permissions/permissions.routes.ts:53-61, backend/src/modules/permissions/permissions.routes.ts:16-30
User and Role Lifecycle
Authorization state changes dynamically as users are assigned to or removed from roles.
- User Creation: Users are stored in Postgres; no FGA tuples are created initially.
- Role Assignment: When a role is assigned via
POST /users/:id/roles, a tuplerole:<roleName>#member@user:<userId>is written to OpenFGA and a record is added to theUserRoletable in Postgres. - Role Deletion: When a role is deleted, the system fetches all tables and generates a list of "delete" tuples to purge all associated permissions from OpenFGA.
Sources: backend/src/modules/users/users.routes.ts:36-54, backend/src/modules/roles/roles.routes.ts:25-42
Conclusion
The Authorization Architecture leverages OpenFGA to provide a flexible and scalable relationship-based access control system. By mapping application roles and resource badges to OpenFGA tuples, the project achieves a high degree of granularity in permission management while maintaining a clear separation between identity management (Postgres) and access policy (OpenFGA).