apavliukov/laravel-authorization
A Laravel authorization package for defining and enforcing access rules. It helps organize roles, permissions, and policy-style checks so you can control which users can perform actions in your application.
Architecture fit v1.6.0 is a strategic narrowing, not a feature release. The package is now scoped to "single-database organizations, roles inside them, one platform admin" — the union of what oohire and opiny actually use. That is a defensible product decision for the author, and the resulting package is smaller, easier to reason about, and has a tighter security surface. But it changes what kind of dependency this is for us.
The load-bearing security property from v1.4.0 — the tenant scope fix that made BelongsToTenant restoration non-bypassable by Eloquent's restore() / Livewire / SerializesModels hydration paths — is not named as removed, and the notes state remaining behaviour is unchanged. That is the single thing we must verify independently rather than accept from release notes, because the same refactor deleted BypassStrategy and the pre-tenancy policy wiring from the authorization path adjacent to it. Confirm the scope/restore behaviour and the accompanying regression tests still exist at v1.6.0.
Two architecture-fit concerns:
Contracts\BypassStrategy, RoleBypass, NoBypass and Authorization::bypassUsing() are gone. The provider now registers one Gate::before with a fixed rule: super-admin passes (under teams, only the global assignment counts), everyone else falls through, and it never returns false. If our app implemented a stricter or conditional bypass — impersonation, service accounts, deny rules, scoped break-glass — that logic has no extension point and must move into application policy code. Note the direction of the change: a bypass gate that never denies is more permissive than a custom strategy that could. That is a security-review item, not a styling detail.TeamResolver and friends are removed, so an application with teams but no tenancy must call setPermissionsTeamId() from its own middleware. TenantScopedPolicy now assumes Tenancy::id() compares against the model's tenancy.foreign_key. Both are conventions we must match or work around; they are no longer configurable seams.The most important architecture statement in the release is procedural, not technical: "Public API is removed in a minor on purpose (two consumers, both the author's)." That tells us semver is not a contract for external consumers, and the package is effectively a private package published publicly. Any integration must be planned on the assumption that future minors may delete whatever we depend on.
Integration feasibility
Mechanically simple, structurally blunt. Every removed symbol is a hard Class not found or Method does not exist at boot or first call — there is no deprecation layer or shim, so failures are loud and enumerable by grep rather than subtle and emergent. That is a favourable failure profile for a one-time upgrade.
Three items need real work rather than a rename:
authorization:sync, authorization:repair-ownership and make:authorization-policy are deleted. The first two typically live in deploy scripts, runbooks, or scheduled tasks; their failure mode is a broken deployment or a silently skipped sync, not a runtime exception. AuthorizationSeeder still syncs permissions and roles and re-derives owner rows on every run, so a replacement exists — but the deploy step must be rewritten, not just deleted, if it was the only thing keeping permissions converged.userCan() semantics. It is now plain $user->can(); Support\UserPermissionMemo and forgetUserPermissions() are gone. Correctness is preserved, but the memoization is not. Policy-heavy rendering (Livewire tables, nav builders, index pages that check per-row abilities) may now issue one or more queries per check. This must be measured against our own hot paths before we accept "performance unchanged."Tenancy\CurrentArea, the authorization.areas config and the Livewire original-route hook are removed. Consumers that referenced CurrentArea::SESSION_KEY — the notes name oohire's Support\Areas and laravel-starter's CurrentLayout — need their own key. This fails loudest in test, but if the key is re-invented inconsistently the production symptom is users being returned to the wrong route after login or a tenant switch. Test it explicitly.We are upgrading from v1.4.0, so we would traverse v1.5.0 without having adopted it. The 1.5.0 tenant-entry knobs (enterable_scope, fallback_to_owned, TenantResolver::choose()) are added and then removed within the same jump. Recommendation: go 1.4.x → 1.6.0 directly and do not adopt 1.5.0 features.
Technical risk
authorization:sync or authorization:repair-ownership breaks with no runtime signal until the deploy fails or permission drift accumulates unnoticed.userCan() losing memoization is a plausible p95 regression in Livewire-heavy admin screens and any policy evaluated per row. Benchmark before and after on representative pages.Gate::before that never returns false replaces a pluggable strategy. Any prior custom bypass must be re-expressed in app code, and the new default must be explicitly reviewed for permissiveness. Super-admin under teams now counts only the global assignment — confirm admin seeding still produces a passing user.TeamResolver, SetPermissionsTeam and resolver wiring and must implement middleware themselves. Confirm we are inside the supported shape before committing.CurrentArea session-key migration. Silent misrouting rather than an exception if re-implemented inconsistently.TenantScopedPolicy FK convention. Policy wiring now hard-codes the tenancy.foreign_key comparison; models with a different or legacy FK name will fail authorization checks without an obvious cause.enterable_scope, fallback_to_owned, areas must be dropped from a published config/authorization.php. Usually inert, but clean them so future readers do not believe they work.Key questions
BypassStrategy, what was its exact rule, and where will it live now? Does the new "never returns false" Gate::before widen access for any role we currently grant?TenantScopedPolicy's tenancy.foreign_key comparison match every model we scope, including any legacy tables with non-standard FK names?userCan() → $user->can() on our top five policy-heavy screens, and is a replacement memo (application-level cache) needed?CurrentArea::SESSION_KEY now, and is there a single definition in our codebase?1.6.0) rather than ^1.6, and do we fork/vendor? What is the exit cost if the author cuts the next thing we depend on?Stack fit Same Laravel queue + Livewire stack as before; the tenancy-isolation blast radius described in the prior assessment (model-carrying jobs, serialized payloads, Livewire model properties) is unchanged and still the dominant integration concern. What v1.6.0 adds is a second, independent workstream: authorization surface removal. The two are largely disjoint and should be staffed and sequenced separately — the tenancy work is behavioural and needs staging soak time, the removal work is mechanical and can land earlier.
Applications that pass IDs rather than models into jobs, and that use the default bypass and a single-database org model, see the smallest delta. Anything with custom bypass logic, teams-without-tenancy, or UI-area routing sees the largest.
Migration path
1.6.0. Given the stated stance on minor-version removals, do not float a caret constraint across this package.TeamResolver, DefaultTeamResolver, CallbackTeamResolver, SetPermissionsTeam, resolveTeamsUsing, BypassStrategy, RoleBypass, NoBypass, BypassGate, bypassUsing, resolveTenantUsing, tenantColumn, systemAbilities, currentTenant, tenantColumnName, UserPermissionMemo, forgetUserPermissions, CurrentArea, choose, clearChoice, TenantChoiceDropped, PermissionSync::plan, apply(prune: true). Include tests and config.composer.json scripts, deploy playbooks and scheduler definitions for authorization:sync, authorization:repair-ownership, make:authorization-policy. Rewrite the deploy step to run AuthorizationSeeder (or your own seeder) instead of deleting it outright.setPermissionsTeamId().enterable_scope, fallback_to_owned, areas from the published config.ModelNotFoundException; plus soft-delete restore semantics.Compatibility Semver-compatible tag, breaking in practice. No migrations, no dependency changes, no schema changes. All breaks are symbol removals and command removals in consumer code and consumer deployment tooling. No deprecation period was offered and none should be expected in future releases.
Sequencing
MissingTenantContextException, ModelNotFoundException, unexpected authorization denials, and permission-query volume for one full business cycle including scheduled-job windows.Operational Impact
Maintenance Maintenance cost rises even though the code surface shrinks. The remaining invariant from 1.4.0 still applies (do not dispatch tenant models outside tenant context) and still needs to live in review guidance or a static check. On top of that, we now carry a vendor-risk obligation: this package deletes public API in minors by design, for an audience of two internal consumers. Track upstream commits, not just tags, and budget a standing review each time a minor lands. Evaluate vendor/fork now — not because 1.6.0 is bad, but because the release notes state plainly that external consumers are not a design constraint. If forking, the fork point is 1.6.0 and it is small enough to be tractable.
Support
Two new runbook entries on top of the tenancy ones. First, authorization anomalies: unexpected access denials after deploy most likely mean TenantScopedPolicy's tenancy.foreign_key comparison does not match a model's actual FK, or a custom bypass was not re-expressed. Second, deploy failures referencing deleted artisan commands — these should be caught in Phase 1, but the runbook should name them. The v1.4.0 runbook entries remain: MissingTenantContextException (fix the dispatch site) and ModelNotFoundException (check strict mode before treating as data loss). Support should also be told that permission drift after a failed seeder step is a possible follow-on symptom of a mis-rewired deploy command. Customer notification obligations for the v1.4.0 cross-tenant exposure remain open and unaffected by this release.
Scaling
No change to hydration cost. The scaling item is new: dropping UserPermissionMemo means permission verdicts are recomputed per call. In a Livewire admin rendering hundreds of rows with per-row ability checks, that is a multiplicative query load against the roles/permissions tables. Benchmark the specific screens, and if a memo is reintroduced, keep it request-scoped and invalidated on role assignment — the package no longer does this for you. The queue-throughput guidance from 1.4.0 also stands: lower tries on high-volume queued classes during rollout so tenant-less dispatch failures surface fast.
Failure modes
MissingTenantContextException under strict, ModelNotFoundException otherwise. Unchanged from 1.4.0.Gate::before never returns false; if it replaces a stricter local strategy, access is silently broader than before.TenantScopedPolicy denies or mis-scopes because a model's foreign key does not match tenancy.foreign_key. Presents as an authorization bug, not a misconfiguration.CurrentArea::SESSION_KEY re-implemented inconsistently; users land on the wrong route after login or tenant switch.withoutTenant() — still the most likely next security bug; unchanged, and still worth a review rule.Ramp-up Two bounded workstreams rather than one. The 1.6.0 removal work is roughly two to four engineer-days for a mid-size codebase: grep-driven, low ambiguity, mostly deletion and one bypass re-implementation. The 1.4.0 tenancy workstream remains about one engineer-week to scan, triage and fix dispatch sites with tests. Add a day for permission-check benchmarking. The durable output is the same as before — model-carrying jobs are a tenant boundary — plus a new one: this dependency's public API is not a contract, so upgrades are projects, not chores.
How can I help you explore Laravel packages today?