artisanpack-ui/rbac
ArtisanPack UI RBAC brings role-based access control to Laravel apps with a ready-made UI for managing roles, permissions, and user assignments. Define access rules and administer authorization from your admin panel, simplifying RBAC setup and day-to-day permission management.
Architecture fit. This package sits in the authorization layer of a Laravel application, extending the native Gate/can() surface rather than replacing it. The v1.1.0 release moves it from a self-contained RBAC implementation toward an extensible one: permissions can now be contributed by external systems (LDAP, feature flags, tenant policy services) through the ap.rbac.abilitiesForUser filter instead of requiring subclasses or forked models. That is a meaningful architectural improvement for multi-tenant or enterprise deployments, where permissions are rarely owned by a single database table. The Gate::before shim firing ap.rbac.checkingAbility also gives us a sanctioned audit seam that does not require wrapping every authorization call site.
Integration feasibility. Integration is straightforward for any Laravel app already using the default Gate. The relevant public surface is small and idiomatic: HasRoles::assignRole() / removeRole() emit actions, HasPermissions::getAbilities() returns a flat string list, and hasPermissionTo() now delegates to it. Because resolution flows through $user->can(), Gate::allows(), and @can, existing controllers, policies, Blade directives, and middleware need no changes to pick up grafted abilities. The cost of adoption is one Composer dependency and, where we want to graft, a single filter listener.
Technical risk. The primary risk is the dependency change: artisanpack-ui/hooks: ^1.3 is now a hard requirement, and hook fire sites are called unconditionally. Any environment that cannot install or autoload that package — restricted vendor allowlists, air-gapped builds, or an app pinned to an older hooks version — will break at runtime, not gracefully at the feature boundary. This is a semver-minor release carrying what is effectively a breaking change for some consumers, so we should treat it as a major for upgrade planning. Secondary risks: (1) the filter is a mutation point on the authorization hot path, so a slow or throwing listener degrades every can() check; (2) the Gate::before audit action fires on every ability check, which is both a performance and a log-volume consideration; (3) listener ordering between multiple grafters is undefined and could produce non-deterministic ability sets if two packages disagree.
Key questions.
artisanpack-ui/hooks already present in our dependency tree, and at what version? If not, what is the approval path for a new transitive (now direct) dependency?hasPermissionTo()? The delegation to getAbilities() changes the extension point.abilitiesForUser listener — does it hit LDAP or a remote flag service per call, and is it cached?checkingAbility action receive enough context (ability, arguments, user) to satisfy our audit requirements, and where is it persisted?HasRoles trait whose assignRole/removeRole calls now trigger unanticipated side effects?Stack fit. Laravel-native, no framework version surprises indicated. It assumes the standard Gate pipeline and the HasRoles/HasPermissions trait pattern on the User model. For our stack this is a low-friction fit provided we are on a Laravel version supported by artisanpack-ui/hooks ^1.3; that compatibility matrix should be confirmed before scheduling.
Migration path. Staged and reversible:
artisanpack-ui/hooks ^1.3 explicitly to composer.json in a dedicated PR so the new hard dependency is visible in review and lockfile diffs, rather than arriving silently as a transitive of the rbac upgrade.@can, Gate::allows(), and policy checks.getAbilities() returns the same set that previously fed hasPermissionTo() for representative users. Any delta indicates a changed resolution path.checkingAbility audit listener in staging only first, measure volume and latency, then enable per environment.Compatibility. Treat the hard-dependency change as breaking for our purposes: it warrants a pinned version range, a changelog note in our internal upgrade log, and a rollback plan (previous rbac version plus removal of the hooks dependency) that we have actually tested. Note that hook fire sites are unconditional, so there is no configuration toggle to run without hooks — the rollback is a version downgrade, not a flag flip.
Sequencing. Dependency baseline → package upgrade → resolution-parity verification → audit listener → single grafting source → remaining grafting sources. Do not combine the dependency PR and the upgrade PR; keeping them separate is what makes a rollback clean.
Maintenance. We now inherit two packages instead of one, and the rbac package's release cadence is coupled to the hooks package. Budget for tracking both. Our own maintenance surface grows slightly: any abilitiesForUser listener we register is application code on the authorization path and needs an owner. The upside is that grafted permissions no longer require patching vendor code, which is a net reduction in long-term maintenance if we were previously subclassing.
Support. Two new failure signatures will reach support: authorization denials caused by a listener returning an incomplete ability list, and errors from a listener that throws. Both present as "the user can't do the thing," which is hard to triage from the outside. We should require that every listener be logged at debug level with its output set, and that the checkingAbility audit trail be queryable by user and ability so support can answer "was this checked, and what was the result?"
Scaling. Two hot-path concerns. The abilitiesForUser filter runs on every resolution of getAbilities(), so a remote call inside it will multiply across a page render with many @can checks — mandate in-process caching with a per-request TTL, and treat uncached remote listeners as a review blocker. The checkingAbility action fires on every Gate::before invocation, which on permission-heavy pages can be hundreds of events per request; if it writes synchronously to a database or log aggregator, it becomes a throughput ceiling. Route it through a queue or sampled buffer.
Failure modes.
can(), potentially turning an authorization check into a 500. Listeners must be wrapped defensively or the call site must tolerate exceptions.checkingAbility fires before resolution, so it records intent, not outcome. If compliance needs the allow/deny result, that requires additional instrumentation.Ramp-up. Low for basic use — the trait and can() semantics are familiar to any Laravel developer. Moderate for the hook layer: engineers need to understand filter-versus-action semantics, listener registration and ordering, and the fact that the filter mutates the resolved ability list globally for that user. Plan a short internal write-up and one pairing session before enabling grafting in production, and designate an owner for the listener code before it ships.
How can I help you explore Laravel packages today?