This package adds a bouncer at Laravel's access gate.
- Introduction
- Installation
- Usage
- Creating roles and abilities
- Assigning roles to a user
- Giving a user an ability directly
- Restricting an ability to a model
- Allowing a user or role to "own" a model
- Retracting a role from a user
- Removing an ability
- Checking a user's roles
- Getting all abilities for a user
- Authorizing users
- Blade directives
- Refreshing the cache
- Configuration
- Cheat sheet
- Alternative
- License
Bouncer provides a mechanism to handle roles and abilities in Laravel's ACL. With an expressive and fluent syntax, it stays out of your way as much as possible: use it when you want, ignore it when you don't.
For a quick, glanceable list of Bouncer's features, check out the cheat sheet.
Bouncer works well with other abilities you have hard-coded in your own app. Your code always takes precedence: if your code allows an action, the bouncer will not interfere.
Once installed, you can simply tell the bouncer what you want to allow at the gate:
// Give a user the ability to create posts
Bouncer::allow($user)->to('create', Post::class);
// Alternatively, do it through a role
Bouncer::allow('admin')->to('create', Post::class);
Bouncer::assign('admin')->to($user);
// You can also grant an ability only to a specific model
Bouncer::allow($user)->to('edit', $post);
When you check abilities at the gate, the bouncer will be consulted first. If he sees an ability that has been granted to the current user (whether directly, or through a role) he'll authorize the check.
Simply install the bouncer package with composer:
$ composer require silber/bouncer v1.0.0-beta.4
In Laravel 5.5, service providers and aliases are automatically registered. If you're using Laravel 5.5, skip ahead directly to step 3 (do not pass go, but do collect $200).
Once the composer installation completes, you can add the service provider and alias the facade. Open config/app.php
, and make the following changes:
-
Add a new item to the
providers
array:Silber\Bouncer\BouncerServiceProvider::class,
-
Add a new item to the
aliases
array:'Bouncer' => Silber\Bouncer\BouncerFacade::class,
This part is optional. If you don't want to use the facade, you can skip step 2.
-
Add the bouncer's trait to your user model:
use Silber\Bouncer\Database\HasRolesAndAbilities; class User extends Model { use HasRolesAndAbilities; }
-
Now, to run the bouncer's migrations, first publish the package's migrations into your app's
migrations
directory, by running the following command:php artisan vendor:publish --tag="bouncer.migrations"
-
Finally, run the migrations:
php artisan migrate
Whenever you use the Bouncer
facade in your code, remember to add this line to your namespace imports at the top of the file:
use Bouncer;
For more information about Laravel Facades, refer to the Laravel documentation.
By default, Bouncer's queries are cached for the current request. For better performance, you may want to enable cross-request caching.
Adding roles and abilities to users is made extremely easy. You do not have to create a role or an ability in advance. Simply pass the name of the role/ability, and Bouncer will create it if it doesn't exist.
Note: the examples below all use the
Bouncer
facade. If you don't like facades, you can instead inject an instance ofSilber\Bouncer\Bouncer
into your class.
Let's create a role called admin
and give it the ability to ban-users
from our site:
Bouncer::allow('admin')->to('ban-users');
That's it. Behind the scenes, Bouncer will create both a Role
model and an Ability
model for you.
To now give the admin
role to a user, simply tell the bouncer that the given user should be assigned the admin role:
Bouncer::assign('admin')->to($user);
Alternatively, you can call the assign
method directly on the user:
$user->assign('admin');
Sometimes you might want to give a user an ability directly, without using a role:
Bouncer::allow($user)->to('ban-users');
Here too you can accomplish the same directly off of the user:
$user->allow('ban-users');
Sometimes you might want to restrict an ability to a specific model type. Simply pass the model name as a second argument:
Bouncer::allow($user)->to('edit', Post::class);
If you want to restrict the ability to a specific model instance, pass in the actual model instead:
Bouncer::allow($user)->to('edit', $post);
Use the toOwn
method to allow users to manage their own models:
Bouncer::allow($user)->toOwn(Post::class);
Now, when checking at the gate whether the user may perform an action on a given post, the post's user_id
will be compared to the logged-in user's id
(this can be customized). If they match, the gate will allow the action.
The above will grant all abilities on a user's "owned" models. You can restrict the abilities by following it up with a call to the to
method:
Bouncer::allow($user)->toOwn(Post::class)->to('view');
// Or pass it an array of abilities:
Bouncer::allow($user)->toOwn(Post::class)->to(['view', 'update']);
You can also allow users to own all types of models in your application:
Bouncer::allow($user)->toOwnEverything();
// And to restrict ownership to a given ability
Bouncer::allow($user)->toOwnEverything()->to('view');
The bouncer can also retract a previously-assigned role from a user:
Bouncer::retract('admin')->from($user);
Or do it directly on the user:
$user->retract('admin');
The bouncer can also remove an ability previously granted to a user:
Bouncer::disallow($user)->to('ban-users');
Or directly on the user:
$user->disallow('ban-users');
Note: if the user has a role that allows them to
ban-users
they will still have that ability. To disallow it, either remove the ability from the role or retract the role from the user.
If the ability has been granted through a role, tell the bouncer to remove the ability from the role instead:
Bouncer::disallow('admin')->to('ban-users');
To remove an ability for a specific model type, pass in its name as a second argument:
Bouncer::disallow($user)->to('delete', Post::class);
Warning: if the user has an ability to
delete
a specific$post
instance, the code above will not remove that ability. You will have to remove the ability separately - by passing in the actual$post
as a second argument - as shown below.
To remove an ability for a specific model instance, pass in the actual model instead:
Bouncer::disallow($user)->to('delete', $post);
Note: Generally speaking, you should not have a need to check roles directly. It is better to allow a role certain abilities, then check for those abilities instead. If what you need is very general, you can create very broad abilities. For example, an
access-dashboard
ability is always better than checking foradmin
oreditor
roles directly. For the rare occasion that you do want to check a role, that functionality is available here.
The bouncer can check if a user has a specific role:
Bouncer::is($user)->a('moderator');
If the role you're checking starts with a vowel, you might want to use the an
alias method:
Bouncer::is($user)->an('admin');
For the inverse, you can also check if a user doesn't have a specific role:
Bouncer::is($user)->notA('moderator');
Bouncer::is($user)->notAn('admin');
You can check if a user has one of many roles:
Bouncer::is($user)->a('moderator', 'editor');
You can also check if the user has all of the given roles:
Bouncer::is($user)->all('editor', 'moderator');
You can also check if a user has none of the given roles:
Bouncer::is($user)->notAn('editor', 'moderator');
These checks can also be done directly on the user:
$user->isAn('admin');
$user->isA('subscriber');
$user->isNotAn('admin');
$user->isNotA('subscriber');
$user->isAll('editor', 'moderator');
You can get all abilities for a user directly from the user model:
$abilities = $user->getAbilities();
This will return a collection of the user's abilities, including any abilities granted to the user through their roles.
You can get all roles for a user directly from the user model:
$roles = $user->getRoles();
This will return a collection of the user's roles.
Authorizing users is handled directly at Laravel's Gate
, or on the user model ($user->can($ability)
).
For convenience, the bouncer class provides two passthrough methods:
Bouncer::can($ability);
Bouncer::cannot($ability);
These call directly into the Gate
class.
Bouncer does not add its own blade directives. Since Bouncer works directly with Laravel's gate, simply use its @can
directive to check for the current user's abilities:
@can ('update', $post)
<a href="{{ route('post.update', $post) }}">Edit Post</a>
@endcan
Since checking for roles directly is generally not recommended, Bouncer does not ship with a separate directive for that. If you still insist on checking for roles, you can do so using the general @if
directive:
@if ($user->isAn('admin'))
//
@endif
All queries executed by Bouncer are cached for the current request. If you enable cross-request caching, the cache will persist across different requests.
Whenever you need, you can fully refresh the bouncer's cache:
Bouncer::refresh();
Note: fully refreshing the cache for all users uses cache tags if they're available. Not all cache drivers support this. Refer to Laravel's documentation to see if your driver supports cache tags. If your driver does not support cache tags, calling
refresh
might be a little slow, depending on the amount of users in your system.
Alternatively, you can refresh the cache only for a specific user:
Bouncer::refreshFor($user);
Bouncer ships with sensible defaults, so most of the time there should be no need for any configuration. For finer-grained control, Bouncer can be customized by calling various configuration methods on the Bouncer
class.
If you only use one or two of these config options, you can stick them into your main AppServiceProvider
's boot
method. If they start growing, you may create a separate BouncerServiceProvider
class in your app/Providers
directory (remember to register it in the providers
config array).
By default, all queries executed by Bouncer are cached for the current request. For better performance, you may want to use cross-request caching:
Bouncer::cache();
Warning: if you enable cross-request caching, you are responsible to refresh the cache whenever you make changes to user's roles/abilities. For how to refresh the cache, read refreshing the cache.
On the contrary, you may at times wish to completely disable the cache, even within the same request:
Bouncer::dontCache();
This is particularly useful in unit tests, when you want to run assertions against roles/abilities that have just been granted.
To change the database table names used by Bouncer, pass an associative array to the tables
method. The keys should be Bouncer's default table names, and the values should be the table names you wish to use. You do not have to pass in all tables names; only the ones you wish to change.
Bouncer::tables([
'abilities' => 'my_abilities',
'permissions' => 'granted_abilities',
]);
Bouncer's published migration uses the table names from this configuration, so be sure to have these in place before actually running the migration file.
You can easily extend Bouncer's built-in Role
and Ability
models:
use Silber\Bouncer\Database\Ability;
class MyAbility extends Ability
{
// custom code
}
use Silber\Bouncer\Database\Role;
class MyRole extends Role
{
// custom code
}
Alternatively, you can use Bouncer's IsAbility
and IsRole
traits without actually extending any of Bouncer's models:
use Illuminate\Database\Eloquent\Model;
use Silber\Bouncer\Database\Concerns\IsAbility;
class MyAbility extends Model
{
use IsAbility;
// custom code
}
use Illuminate\Database\Eloquent\Model;
use Silber\Bouncer\Database\Concerns\IsRole;
class MyRole extends Model
{
use IsRole;
// custom code
}
If you use the traits instead of extending Bouncer's models, be sure to set the proper $table
name and $fillable
fields yourself.
Regardless of which method you use, the next step is to actually tell Bouncer to use your custom models:
Bouncer::useAbilityModel(MyAbility::class);
Bouncer::useRoleModel(MyRole::class);
By default, Bouncer automatically uses the user model of the default auth guard.
If you're using Bouncer with a non-default guard, and it uses a different user model, you should let Bouncer know about the user model you want to use:
Bouncer::useUserModel(\App\Admin::class);
In Bouncer, the concept of ownership is used to allow users to perform actions on models they "own".
By default, Bouncer will check the model's user_id
against the current user's primary key. If needed, this can be set to a different attribute:
Bouncer::ownedVia('userId');
If different models use different columns for ownership, you can register them separately:
Bouncer::ownedVia(Post::class, 'created_by');
Bouncer::ownedVia(Order::class, 'entered_by');
For greater control, you can pass a closure with your custom logic:
Bouncer::ownedVia(Game::class, function ($game, $user) {
return $game->team_id == $user->team_id;
});
// Adding abilities for users
Bouncer::allow($user)->to('ban-users');
Bouncer::allow($user)->to('edit', Post::class);
Bouncer::allow($user)->to('delete', $post);
Bouncer::allow($user)->everything();
Bouncer::allow($user)->toManage(Post::class);
Bouncer::allow($user)->toManage($post);
Bouncer::allow($user)->to('view')->everything();
Bouncer::allow($user)->toOwn(Post::class);
Bouncer::allow($user)->toOwnEverything();
// Removing abilities uses the same syntax, e.g.
Bouncer::disallow($user)->to('delete', $post);
Bouncer::disallow($user)->toManage(Post::class);
Bouncer::disallow($user)->toOwn(Post::class);
// Adding & removing abilities for roles
Bouncer::allow('admin')->to('ban-users');
Bouncer::disallow('admin')->to('ban-users');
// Re-sync a user's abilities
Bouncer::sync($user)->abilities($abilities);
// Assigning & retracting roles from users
Bouncer::assign('admin')->to($user);
Bouncer::retract('admin')->from($user);
// Re-sync a user's roles
Bouncer::sync($user)->roles($roles);
$boolean = Bouncer::can('ban-users');
$boolean = Bouncer::can('edit', Post::class);
$boolean = Bouncer::can('delete', $post);
$boolean = Bouncer::cannot('ban-users');
$boolean = Bouncer::cannot('edit', Post::class);
$boolean = Bouncer::cannot('delete', $post);
$boolean = Bouncer::is($user)->a('subscriber');
$boolean = Bouncer::is($user)->an('admin');
$boolean = Bouncer::is($user)->notA('subscriber');
$boolean = Bouncer::is($user)->notAn('admin');
$boolean = Bouncer::is($user)->a('moderator', 'editor');
$boolean = Bouncer::is($user)->all('moderator', 'editor');
Bouncer::cache();
Bouncer::dontCache();
Bouncer::refresh();
Bouncer::refreshFor($user);
Some of this functionality is also available directly on the user model:
$user->allow('ban-users');
$user->allow('edit', Post::class);
$user->allow('delete', $post);
$user->disallow('ban-users');
$user->disallow('edit', Post::class);
$user->disallow('delete', $post);
$user->assign('admin');
$user->retract('admin');
$boolean = $user->isAn('admin');
$boolean = $user->isAn('editor', 'moderator');
$boolean = $user->isAll('moderator', 'editor');
$boolean = $user->isNot('subscriber', 'moderator');
$abilities = $user->getAbilities();
Among the bajillion packages that Spatie has so graciously bestowed upon the community, you'll find the excellent laravel-permission package. Like Bouncer, it nicely integrates with Laravel's built-in gate and permission checks, but has a different set of design choices when it comes to syntax, DB structure & features. Povilas Korop did an excellent job comparing the two in an article on Laravel News.
Bouncer is open-sourced software licensed under the MIT license