Policy-Based Access Control (PBAC) System Architecture¶
Requirements¶
- Provide centralized authentication via Keycloak with JWT token validation for all external API requests
- Provide authorization with hierarchical role evaluation across Companies, Projects, and Resources
- Support multi-tenancy with tenant isolation at all levels
- Provide fine-grained resource sharing between users within projects (per-user or public access)
- Implement quota and limit management at company and project levels (credits, storage, API calls)
- Secure inter-service communication with authenticated gRPC calls
- Track all policy changes via event sourcing for audit trail and state reconstruction
- Provide transparent access enforcement at the API Gateway level before requests reach downstream services
- Support dual identity storage: Keycloak for authentication, local database for policy data
- Provide admin panel for policy management
Detailed design¶
Access responsibility allocation zones¶
The system is organized into five access responsibility zones, each handling a specific layer of the security model:
-
Authentication zone is a Keycloak-based identity provider that validates user credentials and issues JWT tokens. It supports multiple client audiences and realm-based multi-tenancy. All external requests must carry a valid JWT token. The system must extract User ID, Tenant ID, and user attributes from token claims.
-
API Gateway authorization zone is the entry point for all external API calls. Before forwarding any request to downstream services, the Gateway must verify that the authenticated user has the required access level for the target resource. The Gateway delegates policy evaluation to the PolicyVault service via an internal call.
-
Policy evaluation zone (PolicyVault) is a dedicated microservice responsible for storing and evaluating all access policies. It maintains the domain model of Users, Companies, Projects, and Resources, and provides the rules engine for hierarchical access checks. All policy state changes are recorded as events for audit purposes.
-
Inter-service security zone ensures that internal communication between microservices within the cluster is authenticated. Each service obtains tokens via the Client Credentials OAuth2 flow and attaches them to every inter-service call. Services that do not have declared dependencies must not be able to communicate with each other.
-
Resource access filtering zone represents fine-grained access control within individual services. This zone determines which specific resources (files, folders, templates) a user can access within a project, based on sharing rules. A user may have access to a project but only see a subset of its resources.
2. Domain entities and relationships¶
The PBAC system is built around four core entities: User, Company, Project, and Resource. These entities form a hierarchy that defines the access model.
User: Represents an authenticated person in the system. A User belongs to zero or more Companies and has access to zero or more Projects. Each User has individual quota limits and group memberships. User identity is managed by Keycloak, while policy-related data (memberships, limits, version) is stored locally.
| Attribute | Description |
|---|---|
| ID | Unique identifier (UUID) |
| First Name / Last Name | User's full name |
| Username | Display name |
| Validated email address | |
| Enabled | Active/inactive status |
| Limits | Per-user quota limits (credits, storage, calls, custom) |
| Projects | Set of projects the user belongs to |
| Companies | Set of companies the user belongs to |
| Groups | Role/group assignments |
Company: Represents an organization that owns projects and manages users. Each Company has a single Owner with full privileges. Other users are assigned one of four scopes (Member, Viewer, Editor, Admin). Companies define global quota limits and per-user quota limits.
| Attribute | Description |
|---|---|
| ID | Unique identifier (string) |
| Name | Company name |
| Owner | User ID of the company owner |
| Users | Map of user memberships with their scope |
| Global Limits | Company-wide quota limits |
| User Limits | Per-user quota overrides within the company |
| Projects | Set of projects owned by the company |
Project: Represents a workspace that contains resources and has user memberships with roles. Projects come in two types:
- Personal Project — owned by a single user, not associated with any company. Has user memberships with roles and shared resources.
- Company Project — owned by a company, associated with a company ID. Has user memberships with roles, shared resources, quota limits (inherited from company), and per-user usage tracking.
| Attribute | Description | Personal | Company |
|---|---|---|---|
| ID | Unique identifier (string) | Yes | Yes |
| Name | Project name | Yes | Yes |
| Owner | User ID of the project owner | Yes | Yes |
| Company ID | Associated company | No | Yes |
| Users | Map of user memberships with roles | Yes | Yes |
| Shared Resources | Map of resources with sharing scope | Yes | Yes |
| Limits | Project-level quota limits | No | Yes |
| Quotas | Per-user quota usage tracking | No | Yes |
Resource: Represents an object in the system that can be shared and access-controlled. Resources are identified by type and path.
| Resource Type | Description | Example |
|---|---|---|
| Single File | An individual file asset | models/v2/weights.bin |
| Folder | A directory containing multiple assets | datasets/training/ |
| Template | A template asset | templates/default |
Resources support parent-child containment: a file inside a folder inherits the folder's sharing scope.
3. Roles and scopes¶
The system defines two levels of role assignment: company-level scopes and project-level roles. In addition, resource-level sharing scopes control fine-grained access to individual resources.
Company-level scopes¶
Each user in a company is assigned one of the following scopes. The Owner is a special implicit role - only one user per company can be the Owner.
| Scope | Description |
|---|---|
| Owner | Implicit. Full control over the company, its users, projects, and settings. Can perform any operation. Only one per company. |
| Admin | Full management capabilities. Can manage users, projects, quotas, and settings within the company. |
| Editor | Can read and write resources, manage project content. Cannot manage company users or settings. |
| Viewer | Read-only access to company resources and projects. |
| Member | Base membership. Acknowledges the user belongs to the company but grants no access to resources. |
Project-level roles¶
Each user in a project is assigned one of the following roles. The Owner is a special implicit role.
| Role | Description |
|---|---|
| Owner | Implicit. Full control over the project, including all resources, user management, and settings. Only one per project. |
| Admin | Full management capabilities within the project. Can manage users, roles, shared resources, and quotas. |
| Contributor | Can read and write resources within the project. Cannot manage project settings or users. |
| Viewer | Read-only access to project resources. |
| Custom | An extensible custom role with a user-defined label. Grants no default access; behavior is defined by the system. |
Resource sharing scopes¶
Each shared resource in a project has one of the following scopes:
| Scope | Description |
|---|---|
| Anyone | The resource is visible to all project members. |
| Personal | The resource is visible only to the project owner and a specified set of user IDs. |
4. Permissions matrix by role¶
4.1 Company-level permissions¶
The following table defines which actions each company scope can perform. Access is evaluated by comparing the user's scope level against the required action level.
| Scope | Read | Write | Admin | Custom |
|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | Yes | No |
| Editor | Yes | Yes | No | No |
| Viewer | Yes | No | No | No |
| Member | No | No | No | No |
4.2 Project-level permissions¶
| Role | Read | Write | Admin | Custom |
|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | Yes | No |
| Contributor | Yes | Yes | No | No |
| Viewer | Yes | No | No | No |
| Custom | No | No | No | No |
4.3 Resource visibility¶
| User type | Shared as Anyone | Shared as Personal |
|---|---|---|
| Project Owner | Visible | Visible |
| Listed in Personal scope | Visible | Visible |
| Other project member | Visible | Not visible |
| Non-member | Not visible | Not visible |
5. Access evaluation rules¶
Access is evaluated hierarchically. A user must pass both company-level and project-level checks to gain access.
5.1 Company-level policy¶
- If the user is the company Owner -> Allow
- If the user is not a member of the company -> Deny (UserNotMemberOfCompany)
- If the user's scope level >= the required action level -> Allow
- Otherwise -> Deny (InsufficientCompanyScope)
5.2 Project-level policy¶
- If the user is the project Owner -> Allow
- If the user is not a member of the project -> Deny (UserNotMemberOfProject)
- If the user's role level >= the required action level -> Allow
- Otherwise -> Deny (AccessDenied)
5.3 Combined access check¶
For a Company Project, both checks are executed sequentially:
- Evaluate company-level policy
- If company-level passed, evaluate project-level policy
- If both passed -> Granted
- If either fails -> Denied with the corresponding error
For a Personal Project (no company), only the project-level policy is evaluated.
5.4 Resource access filtering¶
After the user passes the project-level access check:
- If the user is the project Owner -> all shared resources are accessible
- Otherwise, a resource is accessible if:
- Its scope is Anyone, OR
- Its scope is Personal and the user's ID is in the allowed set
- Parent folder containment: if a resource is inside a shared folder, the folder's scope applies to the resource
5.5 Denial reasons¶
| Error | Meaning |
|---|---|
| UserNotMemberOfCompany | The user is not a member of the company |
| UserNotMemberOfProject | The user is not a member of the project |
| AccessDenied | The user's project role is insufficient for the requested action |
| InsufficientCompanyScope | The user's company scope is insufficient for the requested action |
| AccessLimitExceeded | The requested operation exceeds quota limits |
6. Quota and limit management¶
The system supports quota enforcement at two levels: company and project. Quotas limit usage of shared resources such as compute credits, storage, and API calls.
6.1 Quota types¶
| Quota | Description |
|---|---|
| Credit | Compute credits (e.g., GPU time, generation runs) |
| Size | Storage size (e.g., dataset size, model weights) |
| Calls | API call count (e.g., inference requests) |
| Custom | User-defined quota type with a custom label |
6.2 Limit hierarchy¶
- Company global limits — maximum total usage across all projects in the company
- Company per-user limits — maximum usage for a specific user across all company projects
- Project limits — maximum usage within a single project (defaults to company global limit if not set)
- User limits — per-user limits at the individual level
6.3 Quota check algorithm¶
When a user requests a resource that consumes quota:
- Calculate total company-wide usage for the quota type across all company projects
- Calculate project-level usage for the quota type
- Global remaining = company limit - total company usage
- Project remaining = project limit - project usage
- If global remaining \< requested amount -> Deny (AccessLimitExceeded)
- If project remaining \< requested amount -> Deny (AccessLimitExceeded)
- Otherwise -> Allow, return remaining capacity
7. Policy management operations¶
The system must support the following operations for managing users, companies, projects, and their relationships. All operations must be tracked with versioning and timestamps for audit purposes.
7.1 User management¶
| Operation | Description |
|---|---|
| Create user | Register a new user from email (synced with Keycloak) |
| Block / Unblock user | Disable user access with a reason |
| Delete user | Soft-delete user (cascade removal from companies and projects) |
| Update profile | Modify username, first name, last name, email, enabled status |
| Manage memberships | Add/remove user from companies and projects |
| Set user limits | Configure individual quota limits |
| Find by email | Search users by email address |
| Sync user | Reconcile local data with Keycloak identity |
7.2 Company management¶
| Operation | Description |
|---|---|
| Update company | Modify company name, transfer ownership |
| Manage users | Add/remove users, change their scope |
| Manage projects | Add/remove projects from the company |
| Set global limits | Configure company-wide quota limits |
| Set user limits | Configure per-user quota limits within the company |
| View quota summary | Retrieve quota usage breakdown by project and user |
| Delete company | Cascade removal of all user memberships |
7.3 Project management¶
| Operation | Description |
|---|---|
| Update project | Modify project name, transfer ownership |
| Manage users | Add/remove users, change their role |
| Share resources | Add/remove resources to the shared pool, set sharing scope (Personal/Anyone) |
| Update sharing scope | Change who can see a specific resource |
| Set project limits | Configure project-level quota limits |
| Track usage | Update per-user quota consumption |
| Reset quotas | Reset all usage counters in a project |
| Convert project type | Convert between Personal and Company project |
| List accessible resources | Get the set of resources accessible to a specific user |
| Get resource scope | Get the sharing scope for a specific resource |
| Delete project | Cascade removal of all user memberships |
7.4 Cross-entity operations¶
These operations atomically update both sides of a relationship:
| Operation | Description |
|---|---|
| Add user to company | Updates both User (adds company) and Company (adds user with scope) |
| Remove user from company | Updates both User and Company |
| Add user to project | Updates both User (adds project) and Project (adds user with role) |
| Remove user from project | Updates both User and Project |
| Add project to company | Converts project to company type, updates Company |
| Remove project from company | Converts project to personal type, updates Company |
7.5 Access check operations¶
| Operation | Description |
|---|---|
| Check resource access | Verify if a user can perform an action on a specific resource |
| Check project access | Verify if a user can perform an action on a project |
| Get user's project role | Return the user's role in a specific project |
| Get user's company scope | Return the user's scope in a specific company |
| List available projects | Return all projects accessible to a user with their roles |
| List available companies | Return all companies accessible to a user with their scopes |
8. Event sourcing and audit trail¶
All policy state changes must be recorded as events. Each event contains the timestamp and a version number for optimistic concurrency control.
8.1 Principles¶
- Every state mutation is represented as a command that produces one or more events
- Commands are idempotent: repeating the same change returns an error (e.g., "name already set")
- Each entity maintains a version counter that increments with every event
- Events contain the timestamp of the change and the resulting version
- The current state can be reconstructed by replaying events
8.2 Event categories¶
| Category | Examples |
|---|---|
| User events | User created, blocked, deleted, profile updated, company/project membership changed, limit updated |
| Company events | Name/owner updated, user added/removed, user scope changed, project added/removed, limits updated |
| Project events | Name/owner updated, user added/removed, role changed, resource shared/unshared, scope updated, limits updated, quotas reset, project type converted |
| Integration events | Composite events wrapping multiple entity-level events for cross-entity operations |
9. Authentication and identity integration¶
9.1 Keycloak integration¶
The system uses Keycloak as the external identity provider. Authentication and identity management work as follows:
- User authentication: All external API requests must include a valid JWT token issued by Keycloak. The system validates the token signature, issuer, and audience.
- Multi-audience support: The system must support multiple Keycloak clients (audiences). The correct validation scheme is selected based on the JWT
audorazpclaim. - Multi-tenancy: Tenants are identified by Keycloak realms. The tenant ID is extracted from a dedicated JWT claim (
tnt) or parsed from the issuer URL. - Dual storage: User identity (name, email, credentials) is managed by Keycloak. Policy data (memberships, limits, version) is stored locally. Changes to user profile must be synchronized to both stores.
9.2 Service-to-service authentication¶
- Internal services authenticate using the OAuth2 Client Credentials flow
- Each service has a dedicated Keycloak client ID and secret
- Tokens are cached locally with automatic refresh before expiry
- Every inter-service call must include a valid Bearer token
9.3 Identity claims¶
The system must extract the following information from JWT tokens:
| Claim | Required | Purpose |
|---|---|---|
| User ID | Yes | Identifies the authenticated user (from sub, oid, uid, or sid claims) |
| Tenant ID | Yes | Identifies the tenant (from tnt claim or issuer URL) |
| No | Used for search and audit context | |
| Display name | No | Used for UI display |
10. Permissions summary by service¶
The Xavier.Backend platform comprises multiple microservices. The following table summarizes which roles can perform which types of actions on each service.
| Service | Read | Write | Manage | Audit |
|---|---|---|---|---|
| Gateway API | Viewers+ | Contributors+ | Admins | Admins |
| Model Registry | Viewers+ | Contributors+ | Admins | Admins |
| Assets | Viewers+ | Contributors+ | Admins | Admins |
| Generation | Viewers+ | Contributors+ | Admins | Admins |
| Workflows | Viewers+ | Contributors+ | Admins | Admins |
| Notifications | All members | System only | Admins | Admins |
| Telemetry | All members | System only | Admins | Admins |
| PolicyVault | Internal only | Internal only | Admins | Admins |
| Admin Panel | Admins | Admins | Admins | Admins |
"Viewers+" means Viewer role and above (Viewer, Contributor, Admin, Owner). "Contributors+" means Contributor role and above (Contributor, Admin, Owner).
11. Permissions matrix by feature¶
| Feature / Role | Member | Viewer | Contributor / Editor | Admin | Owner |
|---|---|---|---|---|---|
| Company | |||||
| View company info | No | Read | Read | Read | Read |
| Manage company users | No | No | No | Manage | Manage |
| Manage company projects | No | No | No | Manage | Manage |
| Manage company quotas | No | No | No | Manage | Manage |
| Transfer company ownership | No | No | No | No | Manage |
| Project | |||||
| View project resources | No | Read | Read | Read | Read |
| Create/edit resources | No | No | Manage | Manage | Manage |
| Manage project users | No | No | No | Manage | Manage |
| Share/unshare resources | No | No | No | Manage | Manage |
| Manage project quotas | No | No | No | Manage | Manage |
| Convert project type | No | No | No | No | Manage |
| Transfer project ownership | No | No | No | No | Manage |
| Resources | |||||
| View shared resource (Anyone scope) | No | Read | Read | Read | Read |
| View shared resource (Personal scope) | Only if listed | Only if listed | Only if listed | Only if listed | Read |
| Modify shared resource | No | No | Manage | Manage | Manage |
12. Use cases¶
UC-1: User requests a model from the Model Registry¶
- User sends a request to the Gateway API with a JWT token
- Gateway validates the JWT token (Authentication zone)
- Gateway extracts User ID and Tenant ID from claims
- Gateway calls PolicyVault to check if the user has Read access to the model resource in the target project
- PolicyVault evaluates: company policy -> project policy -> resource scope
- If access is granted, Gateway forwards the request to the Model Registry service
- Model Registry returns the model to the user via the Gateway
UC-2: Admin adds a user to a company¶
- Admin sends a request to add a user to a company with a specific scope
- Gateway verifies the admin has Admin scope in the target company
- PolicyVault executes a cross-entity operation: updates both User (adds company) and Company (adds user with scope)
- Events are recorded: UserCompanyAdded and CompanyUserAdded
- Both entities' version numbers are incremented
UC-3: User shares a resource with specific colleagues¶
- User (project Admin or Owner) shares a resource in a project with a Personal scope, listing specific user IDs
- PolicyVault updates the project's shared resources map
- Only the listed users and the project owner can now access this resource
- Other project members cannot see this resource
UC-4: System checks quota before resource allocation¶
- A service needs to allocate compute credits for a user
- The service calls PolicyVault to check quota limits
- PolicyVault calculates: company-wide remaining credits and project-level remaining credits
- If both limits have sufficient capacity, the operation proceeds
- If either limit is exceeded, the operation is denied with the remaining capacity
UC-5: Converting a personal project to a company project¶
- An admin requests to convert a personal project into a company project
- PolicyVault verifies the user has admin access to the target company
- The project is updated: CompanyId is set, quota limits are initialized from company defaults
- Events are recorded for the conversion
- The company's project set is updated to include the new project