Implementing Roles & Permissions in Laravel

September 19, 2017 By - Usama Muneer | 276

    Introduction

    In this article, we’ll learn how to implement & setup roles and permissions in Laravel. There are alot of packages which handle this stuff for you by just pulling them in via composer, setting them up and you’re good to go. But what I feel is these packages often comes with too much juice which I don’t really need.

    What if you only need to setup a real simple roles and permissions setup for your project. with these packages you’ll have less options to customize as per your needs. Some people often refer this as re-inventing the wheel, but as a matter of fact. Its not.

    We’ll try to cover almost every little thing for setting up roles and permissions. So Let’s straight dive in and see what are we covering in this article.

    Database Structure & Migrations

    Relationships between Models

    Custom Directives for views

    Assigning roles and permissions to users

    Setting up Roles & Permissions middleware

    There’s so much to cover in this article, Let’s start by a fresh Laravel install.

    Setting Up

    Open up your terminal and create a new Laravel project by typing in the following command

    $ laravel new roles-permissions

    DYI Config

    Setup and config your database with your project and head over to the next step.

    Making up our Authentication scaffolding

    Lets start by making our authentication scaffolding:

    $ php artisan make:auth

    Models & Migrations

    Start of by creating the required models and migrations for this project.

    In the terminal, type in:

    $ php artisan make:model Permission -m
    $ php artisan make:model Role -m

    As you may know, -m flag will create a migration file for the model. Now you’ll have two new migration files waiting for you to add new fields. Run the following command and migrate your database.

    $ php artisan migrate

    Edit the Permission migration file

    Now, for the permissions table, we only need two fields, an id, a slug and a name. Let’s add these in our migration file and our schema should look like

    Schema::create('permissions', function (Blueprint $table) {
        $table->increments('id');
        $table->string('slug'); //edit-posts
        $table->string('name'); // edit posts
        $table->timestamps();
    });

    Same for the Role migration file

    We’ll have the same set of fields for the roles table as well.

        Schema::create('roles', function (Blueprint $table) {
            $table->increments('id');
            $table->string('slug'); //web-developer
            $table->string('name'); //Web Developer
            $table->timestamps();
        });

    Adding pivot tables

    As our understanding, we need to have the pivot tables for the following set of rules.

    User can have Permission

    For this first pivot table, we’ll create a new migration file for the table users_permissions

    $ php artisan make:migration create_users_permissions_table --create=users_permissions

    For this pivot table between users and permissions, our schema should look like

    Schema::table('users_permissions', function (Blueprint $table) {
        $table->integer('user_id')->unsigned();
        $table->integer('permission_id')->unsigned();
    
     //FOREIGN KEY CONSTRAINTS
        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
     
    //SETTING THE PRIMARY KEYS
        $table->primary(['user_id','permission_id']);
    });

    Here, we’ve set the foreign key constraints in order to delete the corresponding records if a user or permission is deleted.  Primary keys for this table are user_id and permission_id.

    User can have Role

    Now let’s create a pivot table for users_roles.

    $ php artisan make:migration create_users_roles_table --create=users_roles

    The fields inside this table will pretty much the same as in users_permissions table. Our schema for this table will look like:

    Schema::create('users_roles', function (Blueprint $table) {
       $table->integer('user_id')->unsigned();
       $table->integer('role_id')->unsigned();
    
     //FOREIGN KEY CONSTRAINTS
       $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
       $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
    
     //SETTING THE PRIMARY KEYS
       $table->primary(['user_id','role_id']);
    });

    Under a particular Role, User may have specific Permission

    For example, a user may have the permission for post a topic, and an admin may have the permission to edit or delete a topic. In this case, let’s setup a new table for roles_permissions to handle this complexity.

    $ php artisan make:migration create_roles_permissions_table --create=roles_permissions

    The Schema will be like:

    Schema::create('roles_permissions', function (Blueprint $table) {
     $table->integer('role_id')->unsigned();
     $table->integer('permission_id')->unsigned();
    
     //FOREIGN KEY CONSTRAINTS
     $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
     $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
    
     //SETTING THE PRIMARY KEYS
     $table->primary(['role_id','permission_id']);
    });

    If this doesn’t start to make any sense to you just yet, well wait for us to build up the relationships between our tables. Things will get clear I promise.

    Let’s migrate every thing now.

    $ php artisan migrate

    Setting up the relationships

    We’ll start by creating the relationships between roles and permissions table. In our Role.php

    //Role.php
    public function permissions() {
       return $this->belongsToMany(Permission::class,'roles_permissions');
    }

    Same goes for Permission.php in reverse.

    //Permission.php
    public function roles() {
       return $this->belongsToMany(Role::class,'roles_permissions');
    }

    Now, in terms of User. A user has many roles. A user may have many permissions. But potentially a Role has many users and permission has many users. So we need to setup many to many relations in our User model. But what we’ll do is we’ll create a new Trait for this logic so that if we add another model in our project in the future, we can just pull it in.

    Creating a Trait

    Inside of our app directory, let’s create a new directory and name it as Permissions and create a new file namely HasPermissionsTrait.php

    <?php
    namespace App\Permissions;
    
    use App\Permission;
    use App\Role;
    
    trait HasPermissionsTrait {
    
       public function roles() {
          return $this->belongsToMany(Role::class,'users_roles');
    
       }
    
    
       public function permissions() {
          return $this->belongsToMany(Permission::class,'users_permissions');
    
       }
    }

    A nice little trait has been setup to handle user relations. Back in our User model, just import this trait and we’re good to go.

    User hasRole

    Now, we’ll create a new function inside our HasPermissionsTrait.php and name it as hasRole as following

    public function hasRole( ... $roles ) {
       foreach ($roles as $role) {
          if ($this->roles->contains('slug', $role)) {
             return true;
          }
       }
       return false;
    }

    Here, we’re iterating through the roles and checking by the slug field, if that specific role exists. You can check or debug this by using:

    $user = $request->user(); //getting the current logged in user
    dd($user->hasRole('admin','editor')); // and so on

    Checking Permissions

    Now we need to build the ability to give a user some permissions. But wait, here we have a couple of conditions to tackle with:

    User may have individual Permission for some actions.

    Back inside of HasPermissionsTrait.php, we will add some new methods for user permissions:

    protected function hasPermissionTo($permission) {
       return $this->hasPermission($permission);
    }
    
    protected function hasPermission($permission) {
       return (bool) $this->permissions->where('slug', $permission->slug)->count();
    }

    We’ll be utilizing the Laravel’s “can” directive to check if the User have Permission. and instead of using $user->hasPermissionTo(), we’ll use $user->can() To do so, we need to create a new PermissionsServiceProvider for authorization

    $ php artisan make:provider PermissionsServiceProvider

    Register your service provider and head over to the boot method to provide us a Gateway to use can() method.

    //PermissionsServiceProvider.php 
    public function boot()
     {
         Permission::get()->map(function($permission){
    Gate::define($permission->slug, function($user) use ($permission){
       return $user->hasPermissionTo($permission);
    });
         });
     }

    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 learn more about Laravel’s Gate facade at Laravel’s documentation. You can test it out as:

    dd($user->can('permission-slug'));

    User may have Permission through a Role

    To achieve this condition, in our HasPermissionsTrait.

    //HasPermissionsTrait.php
    public function hasPermissionThroughRole($permission) {
       foreach ($permission->roles as $role){
          if($this->roles->contains($role)) {
             return true;
          }
       }
       return false;
    }

    Here, we’re iterating through each permission associated with a role, remember we’ve a many to many relationship setup between roles and permissions table.

    Now the hasPermissionTo() method will check between these two conditions.

    //HasPermissionsTrait.php
    public function hasPermissionTo($permission) {
       return $this->hasPermissionThroughRole($permission) || $this->hasPermission($permission);
    }

    Giving Permissions

    Now let’s say, we want to give a set of permissions to a logged in user, here’s how we can achieve this

    //HasPermissionsTrait.php
    public function givePermissionsTo(... $permissions) {
       $permissions = $this->getAllPermissions($permissions);
       dd($permissions);
       if($permissions === null) {
          return $this;
       }
       $this->permissions()->saveMany($permissions);
       return $this;
    }

    Deleting Permissions

    For deleting or removing permissions from the user scope, we can use the detach method.

    //HasPermissionsTrait.php
    public function deletePermissions( ... $permissions ) {
       $permissions = $this->getAllPermissions($permissions);
       $this->permissions()->detach($permissions);
       return $this;
    }

    This is pretty straight forward, same things will be applied for roles as well. So give it a try and leave us a comment on how you did it.

    Add the Seeders

    In this part, we’ll create seeders for permissions, roles & users and will verify our set of methods accordingly. So let’s start by creating seeders.

    $ php artisan make:seeder PermissionTableSeeder
    $ php artisan make:seeder RoleTableSeeder
    $ php artisan make:seeder UserTableSeeder

    Let’s start by editing adding a couple of records in UserTableSeeder.php

    //UserTableSeeder.php
    $dev_role = Role::where('slug','developer')->first();
    $manager_role = Role::where('slug', 'manager')->first();
    $dev_perm = Permission::where('slug','create-tasks')->first();
    $manager_perm = Permission::where('slug','edit-tasks')->first();
    
    $developer = new User();
    $developer->name = 'Usama Muneer';
    $developer->email = 'usama@thewebtier.com';
    $developer->password = bcrypt('secret');
    $developer->save();
    $developer->roles()->attach($dev_role);
    $developer->permissions()->attach($dev_perm);
    
    
    $manager = new User();
    $manager->name = 'Asad Butt';
    $manager->email = 'asad@thewebtier.com';
    $manager->password = bcrypt('secret');
    $manager->save();
    $manager->roles()->attach($manager_role);
    $manager->permissions()->attach($manager_perm);

    Here, we’re assigning the roles and permissions to our newly created users.

    Head over to the next file, i.e RoleTableSeeder.php

    $dev_permission = Permission::where('slug','create-tasks')->first();
    $manager_permission = Permission::where('slug', 'edit-users')->first();
    
    //RoleTableSeeder.php
    $dev_role = new Role();
    $dev_role->slug = 'developer';
    $dev_role->name = 'Front-end Developer';
    $dev_role->save();
    $dev_role->permissions()->attach($dev_permission);
    
    $manager_role = new Role();
    $manager_role->slug = 'manager';
    $manager_role->name = 'Assistant Manager';
    $manager_role->save();
    $manager_role->permissions()->attach($manager_permission);

    Here, we’re attaching the permissions to the roles, remember we’ve ManyToMany relationship between roles and permissions.

    Last one is PermissionTableSeeder.php

    //PermissionTableSeeder.php
    $dev_role = Role::where('slug','developer')->first();
    $manager_role = Role::where('slug', 'manager')->first();
    
    $createTasks = new Permission();
    $createTasks->slug = 'create-tasks';
    $createTasks->name = 'Create Tasks';
    $createTasks->save();
    $createTasks->roles()->attach($dev_role);
    
    $editUsers = new Permission();
    $editUsers->slug = 'edit-users';
    $editUsers->name = 'Edit Users';
    $editUsers->save();
    $editUsers->roles()->attach($manager_role);

    Now that, all of our seeders are ready to go, lets run our migration with the –seed flag.

    $ php artisan migrate:refresh --seed

    To test this out in your routes files, we can die and dump on:

    $user = $request->user();
    dd($user->hasRole('developer')); //will return true, if user has role
    dd($user->givePermissionsTo('create-tasks')); // will return permission, if not null
    dd($user->can('create-tasks')); // will return true, if user has permission

    Setting up the Custom Blade Directives

    We can utilize the can directive inside of our blade files, which is Laravel’s one of pre-defined method, which can be used for authorizing permissions for users,

    Here we’ll create a role directive to be available in our blade files for checking the roles for users.

    Inside of the boot method in PermissionsServiceProvider.php

    //PermissionsServiceProvider.php
      Blade::directive('role', function ($role){
       return "<?php if(auth()->check() && auth()->user()->hasRole({$role})) :";
      });
      Blade::directive('endrole', function ($role){
       return "<?php endif; ?>";
      });

    Inside of our view files, we can use it like:

    @role('admin')
    
    <h1>Hello from the admin</h1>
    
    @endrole

    Setup the Middleware

    In order to protect our routes, we can setup the middleware to do so.

    $ php artisan make:middleware RoleMiddleware

    Add the middleware into your kernel & setup the handle method as follows

    public function handle($request, Closure $next, $role, $permission = null)
     {
       if(!$request->user()->hasRole($role)) {
         abort(404);
      }
      if($permission !== null && !$request->user()->can($permission)) {
          abort(404);
      }
         return $next($request);
     }

    Right in our routes, we can do something like this

    Route::group(['middleware' => 'role:admin'], function() {
       Route::get('/admin', function() {
          return 'Welcome Admin';
       });
    });

    Simple enough?

    Final words

    Here’s the working Github repo for this Laravel install. clone or download the files if you get stuck anywhere in between. Moreover try out these little tricks yourself and setup roles and permissions for your next Laravel application. Good luck. If you have any queries regarding this article, you may leave a comment below or you can also ask us on Twitter.

     

    blog user
    Usama Muneer

    A web enthusiastic, self-motivated & detail-oriented professional Full-Stack Web Developer from Karachi, Pakistan with experience in developing applications using JavaScript, WordPress & Laravel specifically. Loves to write on different web technologies with an equally useful skill to make some sense out of it.

    Related Posts

    No Related Posts Found