silber/bouncer
Roles and abilities for Laravel with a clean, expressive API. Bouncer manages user authorization, supports Eloquent models, caching, gates and policies, and fluent assignment/checks like can() and is(). Great for flexible, database-driven permissions.
Installation:
composer require silber/bouncer
Publish the migration and config:
php artisan vendor:publish --provider="Bouncer\BouncerServiceProvider"
php artisan migrate
First Use Case: Define a model trait and grant abilities:
use Bouncer\Traits\Authorizable;
class User extends Authenticatable
{
use Authorizable;
}
// Grant a user ability
Bouncer::allow($user)->to('edit', Post::class);
Check Abilities:
if (Bouncer::can($user, 'edit', Post::class)) {
// User can edit posts
}
Bouncer facade (Bouncer::allow(), Bouncer::can(), etc.).Authorizable (for models) and HasAbilities (for checking abilities directly on models).Granting Abilities:
// Single ability
Bouncer::allow($user)->to('publish', Post::class);
// Multiple abilities
Bouncer::allow($user)->to(['publish', 'edit'], Post::class);
// Allow everyone (requires nullable `entity_id`/`entity_type` in DB)
Bouncer::allowEveryone()->to('view', Post::class);
Roles:
$adminRole = Bouncer::createRole('admin');
Bouncer::assign($adminRole)->to($user);
Bouncer::allow($adminRole)->to('delete', Post::class);
Checking Abilities:
// Direct check
if (Bouncer::can($user, 'edit', Post::class)) { ... }
// Model check (if using `HasAbilities` trait)
if ($user->can('edit', $post)) { ... }
// Check any ability
if (Bouncer::canAny($user, ['edit', 'publish'], Post::class)) { ... }
Forbidding Abilities:
Bouncer::forbid($user)->to('delete', Post::class);
// Or via roles
Bouncer::forbid($adminRole)->to('delete', Post::class);
Policies: Bouncer runs after policies by default (configurable via Bouncer::runBeforePolicies()).
// app/Policies/PostPolicy.php
public function update(User $user, Post $post) {
return $post->author_id === $user->id;
}
Bouncer checks will only run if the policy allows the action.
Middleware:
public function handle(Request $request, Closure $next) {
if (!Bouncer::can($request->user(), 'view', Post::class)) {
abort(403);
}
return $next($request);
}
Multi-Tenancy:
// Set global scope
Bouncer::scope()->to($tenantId);
// Temporary scope
Bouncer::scope()->onceTo($tenantId, function () {
// Queries here use $tenantId
});
Seeding:
public function run() {
$adminRole = Bouncer::createRole('admin');
Bouncer::allow($adminRole)->to('*', Post::class); // Wildcard for all abilities
Bouncer::assign($adminRole)->to(User::first());
}
Custom Models:
Bouncer::useRoleModel(CustomRole::class);
Bouncer::useAbilityModel(CustomAbility::class);
Policy Precedence:
Bouncer::runBeforePolicies();
Bouncer::runAfterPolicies().Multi-Tenancy Leaks:
Bouncer::scope()->get() to inspect the current tenant ID.Wildcard Abilities (*):
* allows all abilities for a model. Use sparingly.Bouncer::allow($role)->to('*', Post::class) works, but Bouncer::allow($role)->to('*') does not).Soft Deletes:
Bouncer::forceDelete($role) to clean up pivots.Cache Invalidation:
Bouncer::refresh();
PostgreSQL/Oracle:
$table->json('abilities')->nullable();
Table Prefixes:
Check Abilities:
// Dump all abilities for a user
dd(Bouncer::abilitiesFor($user));
// Dump roles for a user
dd(Bouncer::rolesFor($user));
Log Denied Checks:
Bouncer::setLogger(function ($message) {
Log::debug($message);
});
Inspect Scopes:
// Get current tenant ID
$tenantId = Bouncer::scope()->get();
Common Errors:
Bouncer::useRoleModel()/Bouncer::useAbilityModel().Custom Guards:
Bouncer supports Laravel's guard system. Extend the BouncerGuard class to integrate with custom auth:
Bouncer::extend('custom', function () {
return new CustomBouncerGuard();
});
Events:
Listen to Bouncer events (e.g., Bouncer\Events\AbilityGranted):
event(new Bouncer\Events\AbilityGranted($user, 'edit', Post::class));
Macros:
Extend the Bouncer facade:
Bouncer::macro('isAdmin', function ($user) {
return Bouncer::has($user, 'admin');
});
Custom Storage: Override the default clipboard storage (e.g., for caching):
Bouncer::useClipboard(new CustomClipboard());
Morph Map: If using custom models, ensure they’re registered in the morph map:
class CustomRole extends Role {
public function getMorphClass() {
return 'custom_role';
}
}
Avoid N+1 Queries:
Use with() when eager-loading roles/abilities:
$user->load('abilities', 'roles');
Batch Operations:
Use sync() to replace all abilities/roles at once:
Bouncer::syncAbilities($user, ['edit', 'publish'], Post::class);
Disable Caching: For development, disable caching to avoid stale data:
Bouncer::disableCache();
Indexing:
Ensure your permissions table has indexes on:
entity_typeentity_idabilityHow can I help you explore Laravel packages today?