Skip to content

Architecture

Architecture

Cbox ID resolves all authorization through two contracts. This package implements both against spatie/laravel-permission and binds them under the external driver.

The read path — AccessChecker

SpatieAccessChecker answers the platform's authorization questions and supplies token claims:

Contract method Spatie source
can($userId, $permission, $org) hasPermissionTo() (unknown permission → deny, not error)
permissionsFor($userId, $org) getAllPermissions() — direct and role-derived
forToken($userId, $org, $client) getRoleNames() + getAllPermissions()

forToken() is what the OAuth/OIDC token issuer and the UserInfo endpoint call, so your Spatie roles and permissions land in the tokens the platform mints. The clientId is not used — a single Spatie backend is not partitioned per app, so the token carries the subject's full effective set.

The write path — Roles

SpatieRoles is what SCIM provisioning and the access-governance module call to author and assign roles:

Contract method Spatie action
define() Role::findOrCreate()
grantPermission() Role::givePermissionTo(Permission::findOrCreate())
assign() / unassign() assignRole() / removeRole() on the subject
assertAssignableIn() resolve the role, else UnknownRole
assignmentsForSubject() the subject's roles in the team
assignmentsInOrganization() the model_has_roles pivot for the team

The directory bridge — GroupRoleMappings

SpatieGroupRoleMappings maps SCIM/directory groups onto Spatie roles and reconciles the derived grants as membership changes, keeping its own mapping table and a ledger of the grants it made. See group→role mapping.

Model carriers

The Roles contract returns the platform's own Role and RoleAssignment Eloquent models. Those tables do not exist under the external driver, so the adapter returns unsaved instances as typed carriers of the fields the platform reads — id, name, user_id, role_id, organization_id, source. Spatie owns persistence; the carriers are never saved. A role id is the Spatie role's primary key rendered as a string, so it round-trips back to grantPermission/assign/unassign.

Spatie tracks no grant source of its own, so the directory bridge's ledger supplies it: an assignment recorded there is reported as pushed, anything else as manual. That is what access-governance campaigns display, and it is what makes reconciliation safe (see group→role mapping).

Organizations and Spatie teams

Cbox ID is hierarchy-aware and passes an organizationId on every call. How the adapter uses it depends on id-spatie.teams:

  • Teams on — each organization maps onto a Spatie team (the org id is the team id). Before every read and write the adapter pins Spatie's team context to the organization, so a role or assignment is scoped to the organization it belongs to and never leaks across organizations.
  • Teams off — the backend is flat; the organization id is ignored (single-tenant).

The platform's hierarchy roll-down (a role in an ancestor org applying to descendants) is a feature of the builtin driver; a flat Spatie backend does not model it. If you need it, implement it in a custom AccessChecker that composes the ancestor walk with Spatie lookups.