A Practical Guide to Laravel Roles and Permissions

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

  1. User can have permissions
  2. User can have roles
  3. 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.

9 comments on “A Practical Guide to Laravel Roles and Permissions

  1. Hello hope you will be alright and doing good. Your tutorila about roles and permissions is very helpful but I am facing problem while implementing Roles and Permissions in REST FUL APi. in normal web we can use Spaite or laratrust package and check user Roles and Roles permission using blade syntax but in API how we can implement it? Thanks

  2. This directive bellow worked just fine
    @role(‘project-manager’)
    Only project manager can see this div
    @endrole

    Can I do something like that for permissions ?

    1. Yes you can do but you have to register a new blade directive and then add the permissions logic in it.

      I would prefer to create a helper function which will check the permission access and return true or false.

      Then you can use that helper function in blade directive.

      Hope answer the question.

      Thanks

  3. Thanks for example, but in that case User can has Permission at the same time doesn’t has Role which Permission bind to intermediate table and opposite.

  4. I have two errors in the RoleMiddelware. Both the hasRole and the can methods are marked as undefined.

    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);
    }

    Do I have to add something to make it work ? I checked every step several times but I cannot find what is missing…

  5. Thanks,
    I learned a lot from this tutorial.
    As I am new to laravel,
    I understood using trait and other things by your explanation.
    Best regards
    Sayed

  6. I have spent many hours every day for the last few weeks trying to understand roles, permissions, policies, gates, middleware and custom blade directives in an effort to create the logic you explained in this tutorial. This is extremely helpful, well presented and explained, and I thank you for your walking through this process step by step with great explanations throughout.

    I have really learned a lot here and can’t wait to give this a whirl. Thank you again!

  7. Excellent tutorial! Worked wonders for me.

    One minor thing was:

    php artisan make:seed PermissionSeeder
    php artisan make:seed RoleSeeder
    php artisan make:seed UserSeeder

    Should be:

    php artisan make:seeder PermissionSeeder
    php artisan make:seeder RoleSeeder
    php artisan make:seeder UserSeeder

    Make:seed worked to generate the actual files, but it didn’t register them as seeders for me. I made it work by deleting the files and running make:seeder and then remaking the files.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

When sharing a code snippet please wrap you code with pre tag and add a class code-block to it like below.
<pre class="code-block">you code here</pre>

*
*

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Your little help will keep this site alive and help us to produce quality content for you.