A Practical Guide to Laravel Roles and Permissions
Laravel roles and permissions are the most important part of any Laravel based web application in which you want to add restrictions to the usage of your application. If you google for Laravel Roles and Permissions, you will find a number of packages to add this kind of functionality. You can pull these packages into your application using Composer and with few settings, you are ready to use them.
Most of the time, these packages come with some extra features which you might not use at all in your application. What if you just want a simple to use roles and permissions management system in your application which you can tailor according to your specific requirements. When it comes to implementing a feature that is already available through the composer, many people will call this as re-inventing the wheel.
In my opinion, when I have to implement some simple functionality, why should I bother to increase my dependencies list. Also by implementing some simple features by yourself, it will give you full control in the future to change the behaviour whenever your requirements change.
In this article, I will walk you through how you can implement roles and permission by yourself with step by step instructions.
Setting Up Laravel Application
We will start this tutorial by creating a new Laravel application using the below composer command.
composer create-project laravel/laravel RolesAndPermissions
How to Install Laravel
Follow our How to Install Laravel 6 on Windows and Linux guide for detailed and step by step instructions.
Once you have the application generated, move to the RolesAndPermissions folder and setup the database credentials in your .env
file.
Generate Authentication Scaffold
In Laravel 6, the make:auth
command and all frontend preset have been moved to a stand-alone package laravel/ui
. We will pull this package by running the below command.
composer require laravel/ui --dev
When you have the package added to your application, run the below command to compile and generate the required assets for front-end templates.
npm install && npm run dev
Now, we will generate the default auth scaffold we use the have in pre Laravel 6 versions. Run below command to generate the authentication scaffold.
php artisan ui vue --auth
Once you have this command executed, spin up the built-in PHP server using php artisan serve
command and visit the localhost:8000
.
Generating Models and Migrations
In this section, we will generate two new models called Role and Permission along with their migrations. To create these models we will run the below two commands in our command line terminal.
php artisan make:model Role -m php artisan make:model Permission -m
The -m
flag will generate the migrations along with the model class. Now we will open the migration files for roles and permissions and update them with the database table structure we want to implement.
Open the roles migration file from database/migrations folder and update with the below one.
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateRolesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('roles', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('slug'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('roles'); } }
In this migration file, we simply add the role name
and slug
field. Now open the permissions migration file and update with the below one.
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreatePermissionsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('permissions', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('slug'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('permissions'); } }
Same as the roles migration we added two fields name
and slug
. Super simple.
Adding Required Pivot Tables
Before migrating the tables created earlier, let’s define some relationships which would like to implement for roles and permissions
- User can have permissions
- User can have roles
- A role can have permissions
From the above three relationships, we will need to add three pivot tables to create a Many To Many relationship among User, Role and Permission models.
Let’s create migrations for these pivot tables. Firstly we will create a table for the link between user and permissions. Run below command to generate the required migration file.
php artisan make:migration create_users_permissions_table
Open the newly created create_users_permissions_table
file and update with the below ones.
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersPermissionsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users_permissions', function (Blueprint $table) { $table->unsignedInteger('user_id'); $table->unsignedInteger('permission_id'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade'); $table->primary(['user_id','permission_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users_permissions'); } }
In this migration, we have defined two columns user_id
and permission_id
with the foreign keys on their respective tables. Lastly, we have defined the primary keys for those two fields.
Next, we will create a link between users and the roles table. For this, run below command to generate the required migration file.
php artisan make:migration create_users_roles_table
Open the generated migration file create_users_roles_table
and update with the below ones.
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersRolesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users_roles', function (Blueprint $table) { $table->unsignedInteger('user_id'); $table->unsignedInteger('role_id'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); $table->primary(['user_id','role_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users_roles'); } }
Similar to previous migration, again we have two fields user_id
and role_id
, with foreign keys on their respective tables and primary keys.
Next, we will create a pivot table between the roles and permissions, run below command to generate the migration file.
php artisan make:migration create_roles_permissions_table
Open the create_roles_permissions_table
migration file and update with the below code.
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateRolesPermissionsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('roles_permissions', function (Blueprint $table) { $table->unsignedInteger('role_id'); $table->unsignedInteger('permission_id'); $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade'); $table->primary(['role_id','permission_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('roles_permissions'); } }
In this migration, we defined two fields role_id
and permission_id
with foreign and primary keys.
Now, we have all the required database tables, to create these tables in a database run the below command to migrate changes.
php artisan migrate
If all goes well for you, you will see the roles, permissions, and three pivot tables in your database.
Laravel Roles and Permissions Relationship
In this section, we will set up the relationship for roles and permissions. For users, we will add the relationship in the next section. Open the Role.php
model file and add the below belongsToMany relationship between roles and permissions.
namespace App; use Illuminate\Database\Eloquent\Model; class Role extends Model { public function permissions() { return $this->belongsToMany(Permission::class,'roles_permissions'); } }
Next, open the Permission.php
model file and update with the below one.
namespace App; use Illuminate\Database\Eloquent\Model; class Permission extends Model { public function roles() { return $this->belongsToMany(Role::class,'roles_permissions'); } }
By updating these two models, we have defined the many to many relationships between Roles and Permissions.
HasRolesAndPermissions Trait for User Model
Now for a User model, a user can have many permissions and a user can also have many roles. Same for the reverse relationship, a role can have many users and permission can have many users. So we need to create many to many relationships in the User model.
For the sake of clean code, I will create these relationships in a trait and then use the trait in the User model. We can also reuse this trait, later if we add any model to our application which requires roles and permissions.
In the app
folder, create a new folder and name it Traits. In this file, we will create a new PHP file and name it HasRolesAndPermissions.php.
Once you have the file created, update the file with the below code.
namespace App\Traits; use App\Role; use App\Permission; trait HasRolesAndPermissions { /** * @return mixed */ public function roles() { return $this->belongsToMany(Role::class,'users_roles'); } /** * @return mixed */ public function permissions() { return $this->belongsToMany(Permission::class,'users_permissions'); } }
In this trait, we have defined the roles
and permissions
relationship on their respective models. Now to use this trait in your User model, open the User.php
model class and update with the below.
namespace App; use App\Traits\HasRolesAndPermissions; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use Notifiable, HasRolesAndPermissions; // Our new trait /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; }
User hasRole
Now to check if a current logged in user has a role, we will add a new function in HasRolesAndPermissions trait. Open this trait file and add the below function in it.
/** * @param mixed ...$roles * @return bool */ public function hasRole(... $roles ) { foreach ($roles as $role) { if ($this->roles->contains('slug', $role)) { return true; } } return false; }
In this function, we are passing $roles
array and running a for each loop on each role to check if the current user’s roles contain the given role.
User hasPermission
Next, for checking the permission for a current user, we will add the below two methods in our HasRolesAndPermissions trait.
/** * @param $permission * @return bool */ protected function hasPermission($permission) { return (bool) $this->permissions->where('slug', $permission->slug)->count(); } /** * @param $permission * @return bool */ protected function hasPermissionTo($permission) { return $this->hasPermission($permission); }
The above method will check if the user’s permissions contain the given permission, if yes then it will return true
otherwise false
.
User hasPermissionThroughRole
As we know, we have many to many relationships between roles and permissions. This enables us to check if a user has permission through its role. To implement this we will a new function in our HasRolesAndPermissions trait. Add the below method in your trait file.
/** * @param $permission * @return bool */ public function hasPermissionThroughRole($permission) { foreach ($permission->roles as $role){ if($this->roles->contains($role)) { return true; } } return false; }
This function checks if the permission’s role is attached to the user or not. Now the hasPermissionTo()
the method will check between these two conditions.
Update the hasPermissionTo method like below.
/** * @param $permission * @return bool */ protected function hasPermissionTo($permission) { return $this->hasPermissionThroughRole($permission) || $this->hasPermission($permission); }
Now, we have one method which will check if a user has the permissions directly or through a role. We will use this method to add a custom blade directive later in this post.
Giving Permissions
Now let’s say we want to attach some permissions to the current user. We will add a new method to accomplish this. Add the below methods in your HasRolesAndPermissions trait.
/** * @param array $permissions * @return mixed */ protected function getAllPermissions(array $permissions) { return Permission::whereIn('slug',$permissions)->get(); } /** * @param mixed ...$permissions * @return $this */ public function givePermissionsTo(... $permissions) { $permissions = $this->getAllPermissions($permissions); if($permissions === null) { return $this; } $this->permissions()->saveMany($permissions); return $this; }
The first method is to get all permissions based on an array passed. In the second function, we pass permissions as an array and get all permissions from the database based on the array.
Next, we use the permissions()
method to call the saveMany()
method to save the permissions for the current user.
Deleting Permissions
To delete permissions for a user, we pass permissions to our deletePermissions()
method and remove all attached permissions using the detach()
method.
/** * @param mixed ...$permissions * @return $this */ public function deletePermissions(... $permissions ) { $permissions = $this->getAllPermissions($permissions); $this->permissions()->detach($permissions); return $this; } /** * @param mixed ...$permissions * @return HasRolesAndPermissions */ public function refreshPermissions(... $permissions ) { $this->permissions()->detach(); return $this->givePermissionsTo($permissions); }
The second method actually removes all permissions for a user and then reassign the permissions provided for a user.
Adding Seeders
So far we have implemented the basic roles and permission in our Laravel application, yet we haven’t tested anything. To do some quick testing we will add some Seed classes which will add some dummy data to your tables.
php artisan make:seed PermissionSeeder php artisan make:seed RoleSeeder php artisan make:seed UserSeeder
Now open the RoleSeeder class and update with the below one.
use App\Role; use Illuminate\Database\Seeder; class RoleSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $manager = new Role(); $manager->name = 'Project Manager'; $manager->slug = 'project-manager'; $manager->save(); $developer = new Role(); $developer->name = 'Web Developer'; $developer->slug = 'web-developer'; $developer->save(); } }
Next, open the PermissionSeeder class and update with below.
use App\Permission; use Illuminate\Database\Seeder; class PermissionSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $manageUser = new Permission(); $manageUser->name = 'Manage users'; $manageUser->slug = 'manage-users'; $manageUser->save(); $createTasks = new Permission(); $createTasks->name = 'Create Tasks'; $createTasks->slug = 'create-tasks'; $createTasks->save(); } }
Next, in our UserSeeder class, we will create some users and attach roles and permissions to it.
use App\Role; use App\User; use App\Permission; use Illuminate\Database\Seeder; class UserSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $developer = Role::where('slug','web-developer')->first(); $manager = Role::where('slug', 'project-manager')->first(); $createTasks = Permission::where('slug','create-tasks')->first(); $manageUsers = Permission::where('slug','manage-users')->first(); $user1 = new User(); $user1->name = 'Jhon Deo'; $user1->email = '[email protected]'; $user1->password = bcrypt('secret'); $user1->save(); $user1->roles()->attach($developer); $user1->permissions()->attach($createTasks); $user2 = new User(); $user2->name = 'Mike Thomas'; $user2->email = '[email protected]'; $user2->password = bcrypt('secret'); $user2->save(); $user2->roles()->attach($manager); $user2->permissions()->attach($manageUsers); } }
Once you have three seed classes ready, update the DatabaseSeeder class like below.
use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(RoleSeeder::class); $this->call(PermissionSeeder::class); $this->call(UserSeeder::class); } }
Now, in your command line terminal run the below command to persist data to the database.
php artisan db:seed
Test the permissions and roles for a user like below.
$user = App\User::find(1); dd($user->hasRole('web-developer'); // will return true dd($user->hasRole('project-manager');// will return false dd($user->givePermissionsTo('manage-users')); dd($user->hasPermission('manage-users');// will return true
Adding Custom Blade Directives for Roles and Permissions
In this section, we will create a custom blade directive that we can use inside our blade views. To add a custom blade directive, we will create a new Service Provider.
php artisan make:provider RolesServiceProvider
Now, open this newly created RolesServiceProvider and update it with the below code.
namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class RolesServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { Blade::directive('role', function ($role){ return "<?php if(auth()->check() && auth()->user()->hasRole({$role})) :"; }); Blade::directive('endrole', function ($role){ return "<?php endif; ?>"; }); } }
In the above service provider’s boot method, we are declaring custom directive using the Blade:: facade. In our first directive, we are checking if the user is authenticated and the user has the given role. In the second blade directive, we are closing the if statement.
Inside of our view files, we can use it like:
@role('project-manager')
Project Manager Panel
@endrole @role(‘web-developer’)
Web Developer Panel
@endrole
Easy as it is.
So far, we have used the roles in our custom directive. For permissions, we will use the Laravel’s can
directive to check if a User has Permission and instead of using $user->hasPermissionTo()
, we’ll use $user->can()
.
To achieve this functionality, we will create a new service provider and name it PermissionServiceProvider.
php artisan make:provider PermissionServiceProvider
Open this service provider and update it with the below code block.
use App\Permission; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; class PermissionServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { try { Permission::get()->map(function ($permission) { Gate::define($permission->slug, function ($user) use ($permission) { return $user->hasPermissionTo($permission); }); }); } catch (\Exception $e) { report($e); return false; } } }
Here, what we’re doing is, mapping through all permissions, defining that permission slug (in our case) and finally checking if the user has permission. You can now check for user permission like below.
dd($user->can('manage-users')); // will return true
Adding Middlewares for Roles and Permissions
We can create role-specific areas in your web application. For example, you might want to provide access to manage the user’s section just for project managers. For this, we will use the Laravel Middleware. Using the middleware we can add extra control to incoming requests to your application.
To create a middleware for roles, run below command.
php artisan make:middleware RoleMiddleware
Open the newly created, RoleMiddleware class and update it with the below content.
namespace App\Http\Middleware; use Closure; class RoleMiddleware { /** * Handle an incoming request. * @param $request * @param Closure $next * @param $role * @param null $permission * @return mixed */ public function handle($request, Closure $next, $role, $permission = null) { if(!auth()->user()->hasRole($role)) { abort(404); } if($permission !== null && !auth()->user()->can($permission)) { abort(404); } return $next($request); } }
In this middleware, we are checking if the current user doesn’t have the role/permission specified, then return the 404 error page. There are so many possibilities to use roles and permissions in middleware to control the incoming requests, it all depends on your application’s requirements.
Before using this middleware, you have to add it to your App\Http\Kernel.php file.
Update the $routeMiddleware
array-like below.
/** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array */ protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'role' => \App\Http\Middleware\RoleMiddleware::class, // our role middleware ];
Now, you can use the middleware like below.
Route::group(['middleware' => 'role:project-manager'], function() { Route::get('/dashboard', function() { return 'Welcome Project Manager'; }); });
What’s Next
In this post, we looked at how we can easily create the roles and permission functionality without using any specific package. There are endless possibilities of implementing this concept, I just kept it simple so new Laravel users can easily digest the post.
Code Repository
You can find the code base of this series on the Laravel Roles Permissions repository.
If you have any feedback or suggestion to improve this post, please leave it in the comment box below or open a pull request on the repository.