← Security

SECURITY FEATURE

RBAC

Semitexa separates coarse-grained capabilities from fine-grained permission slugs so modules can extend authorization without coupling themselves to one storage model.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Semitexa RBAC is intentionally hybrid: coarse-grained capabilities can be represented as internal bitmask grants for very fast broad checks, while business-facing permission slugs such as products.write or settings.smtp.update handle exact, human-readable authorization decisions.

How it works

The payload access policy can declare both #[RequiresCapability] and #[RequiresPermission]. Authorizer evaluates them in order: authentication first, then capability checks, then slug checks. CapabilityRegistry maps Capability enum cases to bit positions inside integer segments, while SubjectGrantResolver builds the current subject grant set and asks a module-level PermissionProviderInterface for the user's slug permissions. That means the RBAC core owns the evaluation pipeline, but storage and permission catalogs stay owned by the modules that actually know the business domain.

Why it matters

This split avoids two common failures. A pure slug model becomes noisy for broad platform-level rights, while a pure bitmask model becomes opaque for audits and product-specific rules. The hybrid model keeps the hot path compact and machine-friendly, but still gives reviewers and module authors explicit permission names and extension points.

Key concepts

#[RequiresCapability]
Declares a coarse-grained code-level capability check that is evaluated before slug permissions.
CapabilityRegistry
Maps Capability enum cases to bitmask segment and bit positions so capability checks can stay fast and internal.
#[RequiresPermission]
Declares an exact slug-based permission such as users.manage or settings.smtp.update.
PermissionProviderInterface
Contract implemented by domain modules to supply the current user's permission slugs without coupling RBAC to a specific storage backend.
SubjectGrantResolver
Builds the authenticated subject's combined grant set and caches it per request before Authorizer evaluates policy requirements.
module-owned permission catalog
Each module can define and expose its own permission slugs, roles, and assignment rules while the shared authorization pipeline keeps one evaluation model.

RBAC

Hybrid grant model

Capabilities cover broad platform rights, permission slugs cover exact business actions, and any module can add its own permission list by implementing the RBAC provider contract.

#[RequiresCapability(AdminCapability::BackofficeAccess)]
#[RequiresPermission('products.write')]
#[AsProtectedPayload(path: '/admin/products/{id}', methods: ['PUT'])]
class UpdateProductPayload { ... }

// A domain module supplies slug permissions through PermissionProviderInterface.
Permission Admin Editor Viewer
products.read Granted Granted Granted
products.write Granted Granted Denied
users.manage Granted Denied Denied
orders.manage Granted Denied Denied
settings.manage Granted Denied Denied
#[RequiresCapability] #[RequiresPermission] CapabilityRegistry PermissionProviderInterface

Verified against Semitexa Ultimate 2026.09.19.1020

RBAC

Hybrid RBAC: bitmask-backed capabilities for broad checks, slug permissions for exact business rules, and module-owned catalogs behind one authorizer.

How it works

Semitexa separates two authorization layers. Capabilities are coarse-grained platform rights stored as bitmasks, checked with #[RequiresCapability]. Permission slugs are fine-grained business rules registered by any module implementing PermissionProviderInterface and checked with #[RequiresPermission]. The Authorizer resolves both through the SubjectGrantResolver so handlers never touch the grant resolution logic directly.

Why this matters

A single permission model that forces every module to share one storage schema becomes a coupling problem as the application grows. Separating capabilities from slug permissions lets each module own its permission catalog, extend the authorization surface without modifying core tables, and still share one authorizer that keeps the check consistent across the whole application.

© Harold Abelson: "Programs must be written for people to read, and only incidentally for machines to execute."

RBAC Demo Handler Application entry point
<?phpdeclare(strict_types=1);namespace App\Application\Handler\Auth;use App\Application\Payload\Admin\UpdateProductPayload;use App\Application\Resource\Admin\ProductWriteResource;use App\Domain\Catalog\ProductWriterInterface;use Semitexa\Authorization\Attribute\RequiresCapability;use Semitexa\Authorization\Attribute\RequiresPermission;use Semitexa\Core\Attribute\AsPayloadHandler;use Semitexa\Core\Attribute\InjectAsReadonly;use Semitexa\Core\Contract\TypedHandlerInterface;#[RequiresCapability('backoffice.access')]#[RequiresPermission('products.write')]#[AsPayloadHandler(payload: UpdateProductPayload::class, resource: ProductWriteResource::class)]final class RbacHandler implements TypedHandlerInterface{    #[InjectAsReadonly]    protected ProductWriterInterface $writer;    public function handle(UpdateProductPayload $payload, ProductWriteResource $resource): ProductWriteResource    {        return $resource->fromProduct($this->writer->updateFromPayload($payload));    }}

How it works

The payload access policy can declare both #[RequiresCapability] and #[RequiresPermission]. Authorizer evaluates them in order: authentication first, then capability checks, then slug checks. CapabilityRegistry maps Capability enum cases to bit positions inside integer segments, while SubjectGrantResolver builds the current subject grant set and asks a module-level PermissionProviderInterface for the user's slug permissions. That means the RBAC core owns the evaluation pipeline, but storage and permission catalogs stay owned by the modules that actually know the business domain.

Why it matters

This split avoids two common failures. A pure slug model becomes noisy for broad platform-level rights, while a pure bitmask model becomes opaque for audits and product-specific rules. The hybrid model keeps the hot path compact and machine-friendly, but still gives reviewers and module authors explicit permission names and extension points.

Key concepts

#[RequiresCapability]
Declares a coarse-grained code-level capability check that is evaluated before slug permissions.
CapabilityRegistry
Maps Capability enum cases to bitmask segment and bit positions so capability checks can stay fast and internal.
#[RequiresPermission]
Declares an exact slug-based permission such as users.manage or settings.smtp.update.
PermissionProviderInterface
Contract implemented by domain modules to supply the current user's permission slugs without coupling RBAC to a specific storage backend.
SubjectGrantResolver
Builds the authenticated subject's combined grant set and caches it per request before Authorizer evaluates policy requirements.
module-owned permission catalog
Each module can define and expose its own permission slugs, roles, and assignment rules while the shared authorization pipeline keeps one evaluation model.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal