efficiently/authority-controller
AuthorityController provides a Laravel controller trait for role- and ability-based authorization. It offers convenient methods to authorize actions, check permissions, and enforce access rules in controllers, integrating with the Authority package for centralized user authorization.
Install via Composer (the package targets Laravel 4 through 5.3, so pin your Laravel version accordingly):
composer require efficiently/authority-controller
On Laravel 4.x/5.0–5.2 you must manually register Efficiently\AuthorityController\AuthorityControllerServiceProvider in config/app.php; 5.3 will still resolve it via the provider list — do not assume auto-discovery for 5.4+ (it does not exist yet in this era).
Publish the rules file and open it first — it is the heart of the package:
php artisan vendor:publish --provider="Efficiently\AuthorityController\AuthorityControllerServiceProvider"
That yields config/authority.php with three sections you'll spend most of your time in:
roles — closures that identify a user's role(s).aliases — grouped action names ('modify' => ['create','edit','destroy']).rules — the actual permission matrix, keyed by role, mapping actions to targets ('all', 'own', or a closure).Your first useful use case is locking down an admin section in 10 minutes: define an admin role, add 'manage' => 'all' to its rules, then in a controller:
use Efficiently\AuthorityController\Controller;
class PostsController extends Controller
{
public function edit($id)
{
$post = Post::findOrFail($id);
$this->authorize('edit', $post); // throws AccessDeniedException on failure
return view('posts.edit', compact('post'));
}
}
If the request hits the edit action as a non-owner non-admin, the controller short-circuits and the exception is rendered as a 403 by the package's handler.
Controller-level authorization as the default. The idiomatic workflow is to extend the package's base controller and call authorizeResource() in the constructor so every action is implicitly guarded according to a naming convention (index, show, create, store, edit, update, destroy). Then individual actions only need to pass the resource:
public function __construct()
{
$this->authorizeResource(); // wires up per-action rules
}
This mirrors Rails/CanCan and is the biggest day-to-day win — you stop forgetting the authorize call in one action out of eight.
Rules over conditionals. Resist the urge to write if ($user->isAdmin()) in controllers. Every check belongs in config/authority.php as a rule closure, so the policy is centralised and greppable:
'rules' => [
'user' => [
'edit' => 'own',
'delete' => function ($user, \$post) { return $post->isDraft(); },
],
],
Aliases for CRUD groups. Group create, update, destroy under manage (or modify) so rule files don't grow into a wall of repetitive entries. Alias resolution happens before rule matching, so always prefer the alias in can() / authorize() calls to keep semantics stable.
Expose permissions to Blade. Use the facade helpers for conditional UI:
@if (Authority::can('edit', $post))
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endif
Never rely on view-level hiding as the actual security boundary — always pair with $this->authorize() server-side.
Form requests and non-controller services. When a FormRequest needs authorization, call Authority::authorize('update', $this->route('post')) from authorize(). Same call works in jobs, console commands, and API resources.
Multi-resource checks. authorize() and can() accept arrays/collections when a rule should evaluate on each item — handy for bulk actions like destroyMany.
Testing hooks. Swap the rules array in a test's config or use Authority::setUser($user) to drive the same policy matrix your app uses in production; assert 403 responses rather than duplicating permission logic.
php artisan config:cache serialises config/authority.php, and any closures in that file become unserialisable on some PHP versions. Keep rule closures simple; re-run config:clear when debugging mysteriously failing rules.'own' typically depend on a user_id (or similar) on the model — if your foreign key is named differently or nested (post.author_id), the rule silently fails. Explicitly write the comparison instead of relying on 'own' if your schema deviates.Efficiently\AuthorityController\Exceptions\AccessDeniedException needs to be caught in app/Exceptions/Handler.php (or rendered) if you want JSON responses for API routes — otherwise XHR clients get an HTML error page.Auth::user(); anonymous visitors are null and will fail every check. Either force authentication before authorization or add a guest role in roles if you support anonymous read.Post::find() inside a rule. Pass the already-loaded model in from the controller.authorizeResource() runs before $this->middleware() resolves. If you need route-model binding before the check, call $this->authorize(...) explicitly at the top of the action instead of relying on the constructor hook.Authority::can('action', $resource), the resolved role list, and the raw rule key (role.action.target) — 90% of bugs are a rule not matching because of an unlisted alias or a role closure returning false due to a lazy-loaded relation.roles closure config rather than subclassing the controller — the role matrix is one of the few parts of the package that's safely overridable at the config layer.composer update drag in an incompatible Laravel minor.How can I help you explore Laravel packages today?