Skip to main content

Authorization

CRAFT uses OpenFGA for authorization, implementing Relationship-Based Access Control (ReBAC). Unlike traditional RBAC where roles grant flat permission sets, ReBAC models permissions as relationships between users and resources, enabling fine-grained, hierarchical access control.
OpenFGA is the open-source implementation of Google’s Zanzibar authorization system. It evaluates permission checks in single-digit milliseconds, making it suitable for per-request authorization in API endpoints.

Core Concepts

A relationship tuple is the fundamental building block. It states: “This user/group has this relation on this resource.”
Tuples are stored in OpenFGA and queried during permission checks.
The authorization model defines types for every resource in the platform:
TypeServiceDescription
organizationGovernanceTop-level tenant boundary
projectGovernanceResource grouping within an organization
artifactAssetsGenerated outputs (reports, visualizations)
fileAssetsUploaded files and documents
data_connectionAssetsExternal data source connections
mcp_serverAssetsModel Context Protocol servers
api_serverAssetsAPI server registrations
modelAssetsML model registrations
Relations represent the roles a user or group can have on a resource:
RelationScopeDescription
ownerAll typesFull control including deletion
adminOrganization, ProjectAdministrative access, user/project management
memberOrganizationStandard membership with read access
developerProject, ResourcesCreate and manage resources
operatorProject, ResourcesExecute and operate resources
viewerProject, ResourcesRead-only access
Computed permissions are derived from relations using OR logic. For example, can_read is granted to anyone with viewer, operator, developer, admin, or owner relation:
The full set of computed permissions:
PermissionDescription
can_readView the resource
can_writeModify the resource
can_deleteRemove the resource
can_executeRun/invoke the resource (agents, MCP servers, data connections)
can_create_resourcesCreate child resources (project-level)
can_shareShare with other users
can_read_secretsView associated secrets
can_manage_secretsCreate/update/delete secrets
can_read_metadataView resource metadata
can_manage_metadataModify resource metadata
can_manage_projectsCreate/delete projects (organization-level)
can_manage_usersManage user memberships (organization-level)

Permission Inheritance

The authorization model uses OpenFGA’s from keyword to implement hierarchical permission inheritance:
This means an organization admin automatically has appropriate access to all projects and resources within their organization, without needing explicit tuples on each resource:
The inheritance chain is established via parent link tuples:

Computed delegation: can_delete from project

For resource types like data_connection, the schema computes resource-level permissions from project-level permissions. For example, can_delete on a data connection is satisfied if the user has delete rights on the parent project:
This means an organization owner who has can_delete on every project (via the org-from-project chain above) can also delete every project-scoped resource without per-resource tuples.

Service worker permissions on data_connection

Service workers (background tasks like the data-readiness worker, scheduled enrichment runs, and dataset sync jobs) need to invoke data connections. The schema grants can_execute on data_connection to the service-worker role at the project level so workers can POST /assets/data/{resource_uri}/verify and GET /assets/data/{resource_uri}/secret against any connection in their assigned project, without needing a per-connection tuple. This follows the same pattern as can_create_resources on project for service workers: a single role binding at the parent grants narrow operational permissions across all child resources.

Authorization Schema (OpenFGA DSL)

The schema is defined in openfga-schema.fga and compiled to openfga-schema.json for runtime use. Here is a simplified excerpt showing the project type:
When modifying the OpenFGA schema, always update both the .fga DSL file and regenerate the .json runtime file by running ./scripts/generate-openfga-schema.sh. The Governance service reads only the JSON file at startup. Adding new permissions is safe; renaming or removing is a breaking change.

The require_permission Pattern

All API endpoints use the require_permission() dependency to enforce authorization. This is implemented in the shared common package and used consistently across all services:

Permission Check Flow

When require_permission() is invoked, the following occurs:
1

Extract Resource ID

The resource ID is resolved from path parameters, query parameters, or headers based on the configuration.
2

Get Auth Context

The Auth object is extracted from the validated JWT, providing sub (user ID) and teams (group memberships).
3

Check OpenFGA

The permission checker queries OpenFGA with:
  • User: user:{sub} and each group:{group_name}#member
  • Relation: The computed permission (e.g., can_delete)
  • Object: {resource_type}:{resource_id}
If any user or group check returns true, access is granted.
4

Return Result

  • 200/success: Permission granted, the endpoint handler executes
  • 403 Forbidden: Permission denied, the request is rejected
  • 500 Internal Server Error: Permission system misconfigured

Permission API

The Governance service exposes a Permissions API for programmatic permission management:
EndpointMethodDescription
/governance/permissions/checkGETCheck if a user has a specific permission on a resource
/governance/permissions/grantPOSTGrant a relation to a user or group on a resource
/governance/permissions/revokePOSTRevoke a relation from a user or group
/governance/permissions/set-parentPOSTEstablish a parent-child relationship for inheritance
/governance/permissions/delete-allPOSTRemove all permission tuples for a resource (cleanup)
Assets and Utils never call the Permissions API directly. They use the require_permission() dependency, which internally forwards the user’s JWT to the Governance service for permission validation via the auto-generated SDK.

Keycloak Groups and OpenFGA

The bridge between Keycloak (identity) and OpenFGA (authorization) is Keycloak groups:
  1. Users are assigned to groups in Keycloak (e.g., org-admins, project-developers)
  2. A groups mapper on the realm ensures group memberships appear in the JWT groups claim
  3. During bootstrap, permission tuples map groups to OpenFGA relations (e.g., group:org-admins#member -> admin on organization)
  4. At runtime, require_permission() checks both the user’s direct tuples and their group-based tuples
This means adding a user to a Keycloak group is sufficient to grant them the corresponding platform permissions — no separate OpenFGA management is needed.

Next Steps

Authentication

Understand how JWT tokens carry group claims for authorization.

Organizations

See how organization-level permissions are bootstrapped.

Projects

Learn about project-level role assignments and inheritance.

Multi-Tenancy

Explore the full tenant isolation architecture.