Role based permission to Laravel

I am trying to do a role based permission control in a Laravel application. I want to check what actions can some user do, but i can't figure out how to implement gates and policies in my model (the permission description is in the database and are booleans asociated to a table that stores the resource's ids).

This is the database model that im using:

I would like to know if laravel gates is useful in my case, and how can i implement it, if not, how to make a basic middleware that take care of permission control to protect routes (or controllers).

In the table resource i have a uuid that identifies the resources, the alias is the name of the resource and has dot notation values of actions or context of the resource (eg. 'mysystem.users.create', 'mysystem.roles.delete', 'mysystem.users.images.view'). The policy tables has a boolean 'allow' field that describes the permission of users.

Thanks in advance.

This is the way that I implement role based permissions in Laravel using Policies.

Users can have multiple roles. Roles have associated permissions. Each permission allows a specific action on a specific model.

Migrations

Roles table

class CreateRolesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->unique();
            $table->string('label');
            $table->text('description');
            $table->timestamps();
        });
    }
// rest of migration file

Permissions table

class CreatePermissionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('permissions', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->unique();
            $table->string('label');
            $table->text('description');
            $table->timestamps();
        });
    }
// rest of migration file

Permission Role Pivot Table

class CreatePermissionRolePivotTable  extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('permission_role', function (Blueprint $table) {
            $table->integer('permission_id')->unsigned()->index();
            $table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
            $table->integer('role_id')->unsigned()->index();
            $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
            $table->primary(['permission_id', 'role_id']);
        });
    }
// rest of migration file

Role User Pivot Table

class CreateRoleUserPivotTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('role_user', function (Blueprint $table) {
            $table->integer('role_id')->unsigned()->index();
            $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
            $table->integer('user_id')->unsigned()->index();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->primary(['role_id', 'user_id']);
        });
    }
// rest of migration file
Models

User

public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

public function assignRole(Role $role)
    {
        return $this->roles()->save($role);
    }

public function hasRole($role)
    {
        if (is_string($role)) {
            return $this->roles->contains('name', $role);
        }
        return !! $role->intersect($this->roles)->count();
    }

Role

class Role extends Model
{
    protected $guarded = ['id'];
    protected $fillable = array('name', 'label', 'description');
    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }
    public function givePermissionTo(Permission $permission)
    {
        return $this->permissions()->save($permission);
    }
    /**
     * Determine if the user may perform the given permission.
     *
     * @param  Permission $permission
     * @return boolean
     */
    public function hasPermission(Permission $permission, User $user)
    {
        return $this->hasRole($permission->roles);
    }
    /**
     * Determine if the role has the given permission.
     *
     * @param  mixed $permission
     * @return boolean
     */
    public function inRole($permission)
    {
        if (is_string($permission)) {
            return $this->permissions->contains('name', $permission);
        }
        return !! $permission->intersect($this->permissions)->count();
    }
}

Permission

class Permission extends Model
{
    protected $guarded = ['id'];
    protected $fillable = array('name', 'label', 'description');
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
    /**
     * Determine if the permission belongs to the role.
     *
     * @param  mixed $role
     * @return boolean
     */
    public function inRole($role)
    {
        if (is_string($role)) {
            return $this->roles->contains('name', $role);
        }
        return !! $role->intersect($this->roles)->count();
    }
}
Policies

A policy is required for each model. Here is an example policy for a model item. The policy defines the 'rules' for the four actions 'view, create, update, delete.

class ItemPolicy
{
    use HandlesAuthorization;
    /**
     * Determine whether the user can view the item.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function view(User $user)
    {
        $permission = Permission::where('name', 'items-view')->first();
        return $user->hasRole($permission->roles);
    }
    /**
     * Determine whether the user can create items.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        $permission = Permission::where('name', 'items-create')->first();
        return $user->hasRole($permission->roles);
    }
    /**
     * Determine whether the user can update the item.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function update(User $user)
    {
        $permission = Permission::where('name', 'items-update')->first();
        return $user->hasRole($permission->roles);
    }
    /**
     * Determine whether the user can delete the item.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function delete(User $user)
    {
        $permission = Permission::where('name', 'items-delete')->first();
        return $user->hasRole($permission->roles);
    }
}

Register each policy in AuthServiceProvider.php

use App\Item;
use App\Policies\ItemPolicy;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Item::class => ItemPolicy::class,
    ];
// rest of file
Controllers

In each controller, refer to the corresponding authorisation action from the policy.

For example, in the index method of ItemController:

public function index()
{
    $this->authorize('view', Item::class);

    $items = Item::orderBy('name', 'asc')->get();

    return view('items', ['items' => $items]);
}
Views

In your views, you can check if the user has a specific role:

@if (Auth::user()->hasRole('item-administrator'))
// do stuff
@endif

or if a specific permission is required:

@can('create', App\User::class)
// do stuff
@endcan

spatie/laravel-permission: Associate users with roles and , This is the way that I implement role based permissions in Laravel using Policies. Users can have multiple roles. Roles have associated permissions. Each permission allows a specific action on a specific model. 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.

Answer for your Question:how to make a basic middleware that take care of permission control to protect routes (or controllers)?.

Just an Example: Here is the simple role middleware for your routes AdminRole

namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;

class AdminRole
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if(Auth::user()->role->name!=="admin"){ //Check your users' role or permission, in my case only admin role for routes
            return redirect('/access-denied');
        }
        return $next($request);
    }
}

After defining this middleware Update your kernel.php file as

protected $routeMiddleware = [
    ..............
    'admin' =>\App\Http\Middleware\AdminRole::class,
    ...................
];

And to use this route middleware: There are different way to use route middleware but following is one example

Route::group(['middleware' => ['auth','admin']], function () {
    Route::get('/', 'AdminController@index')->name('admin');
});

Note: There are some tools and libraries for roles and permission on laravel but above is the example of creating basic route middle-ware.

Role based permission to Laravel, role and authorization management system. I'll explain how to implement a Laravel authorization system and define users permission based� But you can use spatie / laravel-permission to create this roles and permissions system in laravel 7. It is important to add laravel user roles and permissions mechanism in our large scale application to give the permit to user to specific task. We will see from scratch laravel 7/6 user roles and permissions tutorial.

Because the laravel model did not fit my database so much, I did almost everything again. This is a functional draft in which some functions are missing, the code is not optimized and it may be a bit dirty, but here it is:

proyect/app/Components/Contracts/Gate.php This interface is used to create singleton in AuthServiceProvider.

<?php

namespace App\Components\Contracts;

interface Gate
{
    public function check($resources, $arguments = []);
    public function authorize($resource, $arguments = []);
}

proyect/app/Components/Security/Gate.php This file loads the permissions from the database. This could be improved a lot :(

<?php

namespace App\Components\Security;

use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class Gate implements GateContract
{
    use HandlesAuthorization;

    protected $container;
    protected $userResolver;
    protected $policies = [];

    public function __construct(Container $container, callable $userResolver)
    {
        $this->container    = $container;
        $this->userResolver = $userResolver;
    }

    public function permissionsForUser(User $user)
    {
        $result = User::with(['roles.resources', 'groups.resources', 'policies'])->where('id', $user->id)->first();

        $list = [];

        //role-specific ... the order is important role < group < user permissions
        foreach ($result->roles as $role) {
            foreach ($role->resources as $permission) {
                if (isset($list[$permission->uuid])) {
                    if ($list[$permission->uuid]['on'] == User::ROLE_POLICY) {
                        if ($permission->pivot->allow == false) {
                            $list[$permission->uuid]['allow'] = false;
                        }
                    } else {
                        $list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
                        $list[$permission->uuid]['on']    = User::ROLE_POLICY;
                        $list[$permission->uuid]['id']    = $role->id;
                    }
                } else {
                    $list[$permission->uuid] = [
                        'allow' => ($permission->pivot->allow ? true : false),
                        'on'    => User::ROLE_POLICY,
                        'id'    => $role->id];
                }
            }
        }

        // group-specific
        foreach ($result->groups as $group) {
            foreach ($group->resources as $permission) {
                if (isset($list[$permission->uuid])) {
                    if ($list[$permission->uuid]['on'] == User::GROUP_POLICY) {
                        if ($permission->pivot->allow == false) {
                            $list[$permission->uuid]['allow'] = false;
                        }
                    } else {
                        $list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
                        $list[$permission->uuid]['on']    = User::GROUP_POLICY;
                        $list[$permission->uuid]['id']    = $group->id;
                    }
                } else {
                    $list[$permission->uuid] = [
                        'allow' => ($permission->pivot->allow ? true : false),
                        'on'    => User::GROUP_POLICY,
                        'id'    => $group->id];
                }
            }
        }

        // user-specific policies
        foreach ($result->policies as $permission) {
            if (isset($list[$permission->uuid])) {
                if ($list[$permission->uuid]['on'] == User::USER_POLICY) {
                    if ($permission->pivot->allow == false) {
                        $list[$permission->uuid]['allow'] = false;
                    }
                } else {
                    $list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
                    $list[$permission->uuid]['on']    = User::USER_POLICY;
                    $list[$permission->uuid]['id']    = $result->id;
                }
            } else {
                $list[$permission->uuid] = [
                    'allow' => ($permission->pivot->allow ? true : false),
                    'on'    => User::USER_POLICY,
                    'id'    => $result->id,
                ];
            }
        }

        return $list;
    }

    public function check($resources, $arguments = [])
    {
        $user = $this->resolveUser();

        return collect($resources)->every(function ($resource) use ($user, $arguments) {
            return $this->raw($user, $resource, $arguments);
        });
    }

    protected function raw(User $user, $resource, $arguments = [])
    {
        $list = $user->getPermissionList();

        if (!Resource::isUUID($resource)) {
            if (empty($resource = Resource::byAlias($resource))) {
                return false;
            }
        }

        if (empty($list[$resource->uuid]['allow'])) {
            return false;
        } else {
            return $list[$resource->uuid]['allow'];
        }
    }

  public function authorize($resource, $arguments = [])
    {
        $theUser = $this->resolveUser();

        return $this->raw($this->resolveUser(), $resource, $arguments) ? $this->allow() : $this->deny();
    }

   protected function resolveUser()
    {
        return call_user_func($this->userResolver);
    }
}

proyect/app/Traits/Security/AuthorizesRequests.php This file is added to controller. Allows to use $this->authorize('stuff'); in a controller when is added.

<?php

namespace App\Traits\Security;

use App\Components\Contracts\Gate;

trait AuthorizesRequests
{
  public function authorize($ability, $arguments = [])
    {
        list($ability, $arguments) = $this->parseAbilityAndArguments($ability, $arguments);

        return app(Gate::class)->authorize($ability, $arguments);
    }
}

proyect/app/Providers/AuthServiceProvider.php This file is the same that can be found on proyect/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php, but i changed some parts to add new classe. Here are the important methods:

<?php

namespace App\Providers;

use App\Components\Contracts\Gate as GateContract;
use App\Components\Security\Gate;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Support\ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /* function register() ... */
    /* other methods () */

    protected function registerAccessGate()
    {
        $this->app->singleton(GateContract::class, function ($app) {
            return new Gate($app, function () use ($app) {
                return call_user_func($app['auth']->userResolver());
            });
        });
    }

    /* ... */
}

proyect /app/Http/Middleware/AuthorizeRequest.php This file is used to allow add the 'can' middleware to routes, eg: Route::get('users/', 'Security\UserController@index')->name('users.index')->middleware('can:inet.user.list');

<?php

namespace App\Http\Middleware;

use App\Components\Contracts\Gate;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;

class AuthorizeRequest
{
    protected $auth;
    protected $gate;

    public function __construct(Auth $auth, Gate $gate)
    {
        $this->auth = $auth;
        $this->gate = $gate;
    }

    public function handle($request, Closure $next, $resource, ...$params)
    {
        $this->auth->authenticate();
        $this->gate->authorize($resource, $params);

        return $next($request);
    }
}

but you must overwrite the default value in proyect/app/Http/Kernel.php:

/* ... */
protected $routeMiddleware = [
    'can'        => \App\Http\Middleware\AuthorizeRequest::class,
    /* ... */
];

To use @can('inet.user.list') in a blade template you have to add this lines to proyect/app/Providers/AppServiceProvider.php:

class AppServiceProvider extends ServiceProvider
{
    public function boot()
        Blade::if ('can', function ($resource, ...$params) {
            return app(\App\Components\Contracts\Gate::class)->check($resource, $params);
        });
    }
    /* ... */

User model at proyect/app/Models/Security/User.php

<?php

namespace App\Models\Security;

use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Group;
use App\Models\Security\Resource;
use App\Models\Security\Role;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;

class User extends Authenticatable
{
    use SoftDeletes;
    use Notifiable;

    public $table = 'user';

    const CREATED_AT = 'created_at';
    const UPDATED_AT = 'updated_at';

    // tipos de politicas
    const GROUP_POLICY = 'group_policy';
    const ROLE_POLICY  = 'role_policy';
    const USER_POLICY  = 'user_policy';

    protected $dates = ['deleted_at'];

    public $fillable = [
    ];

    public function policies()
    {
        return $this->belongsToMany(Resource::class, 'user_policy', 'user_id', 'resource_id')
            ->whereNull('user_policy.deleted_at')
            ->withPivot('allow')
            ->withTimestamps();
    }

    public function groups()
    {
        return $this->belongsToMany(Group::class, 'user_group', 'user_id', 'group_id')
            ->whereNull('user_group.deleted_at')
            ->withTimestamps();
    }

    public function roles()
    {
        return $this->belongsToMany(Role::class, 'user_role', 'user_id', 'role_id')
            ->whereNull('user_role.deleted_at')
            ->withTimestamps();
    }

    public function getPermissionList()
    {
        return app(GateContract::class)->permissionsForUser($this);
    }
}

Group model at proyect/app/Models/Security/Group.php THis is the same than Role, change only names

<?php

namespace App\Models\Security;

use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Group extends Model
{
    use SoftDeletes;

    public $table = 'group';

    const CREATED_AT = 'created_at';
    const UPDATED_AT = 'updated_at';

    protected $dates = ['deleted_at'];

    public $fillable = [
        'name',
    ];

    public static $rules = [
    ];

    public function users()
    {
        return $this->hasMany(User::class);
    }

    public function resources()
    {
        return $this->belongsToMany(Resource::class, 'group_policy', 'group_id', 'resource_id')
            ->whereNull('group_policy.deleted_at')
            ->withPivot('allow')
            ->withTimestamps();
    }
}

Resource Model proyect/app/Models/Security/Resource.php

<?php

namespace App\Models\Security;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Resource extends Model
{
    use SoftDeletes;

    public $table = 'resource';

    const CREATED_AT = 'created_at';
    const UPDATED_AT = 'updated_at';

    protected $dates = ['deleted_at'];

    public $fillable = [
        'alias',
        'uuid',
        'type',
    ];

    public static $rules = [
    ];

    public static function isUUID($value)
    {
        $UUIDv4 = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i';

        return preg_match($UUIDv4, $value);
    }

    public static function byAlias($value)
    {
        return Resource::where('alias', $value)->first();
    }
}

There are a lot of things that I have not put here, but this is what I have so far

Laravel authorization and roles permission management, A role can be assigned to any user: $user->assignRole('writer'); // You can also assign multiple roles at once $user->assignRole('writer', 'admin'); // or as an� This is the way that I implement role based permissions in Laravel using Policies. Users can have multiple roles. Roles have associated permissions. Each permission allows a specific action on a specific model.

The problem i find with trying to combine permissions from a db with policies is when it comes to the ownership of a record.

Ultimately in our code we would like to check access to a resource using permission only. This is because as the list of roles grows we don't want to have to keep adding checks for these roles to the codebase.

If we have a users table we may want 'admin' (role) to be able to update all user records but a 'basic' user to only be able to update their own user record. We would like to be able to control this access SOLELY using the database.

However, if you have an 'update_user' permission then do you give it to both roles? If you don't give it to the basic user role then the request won't get as far as the policy to check ownership.

Hence, you cannot revoke access for a basic user to update their record from the db alone. Also the meaning of 'update_user' in the permissions table now implies the ability to update ANY user.

SOLUTION?

Add extra permissions to cater for the case where a user owns the record.

So you could have permissions to 'update_user' AND 'update_own_user'.

The 'admin' user would have the first permission whilst the 'basic' user would have the second one.

Then in the policy we check for the 'update_user' permission first and if it's not present we check for the 'update_own_user'.

If the 'update_own_user' permission is present then we check ownership. Otherwise we return false.

The solution will work but it seems ugly to have to have manage 'own' permissions in the db.

Using permissions via roles, We need model to make users roles and permissions. So let's create our model using below command. php artisan make:model Permission -m� – 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.

Laravel 7.x User Roles and Permissions Tutorial, i will explain how to implement User Roles and Permissions(ACL) using spatie/ laravel-permission composer package. Spatie role permission� Spatie role permission composer package provide way to create acl in laravel 5.8. they provide how to assign role to user, how to assign permission to user and how to assign permission assign to roles. i will write step by step creating roles and permissions in laravel 5.8 application.

Laravel 5.8 User Roles and Permissions Tutorial, 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� This tutorial gives a step by step guide on how to setup Role based authentication in Laravel along with it’s native authentication system. We are using Laravel 5.5 for this tutorial. Role Based Authentication / Authorization. Let’s just take a moment to understand role based authorization and what you can achieve with this.

Implementing Roles & Permissions in Laravel, Laravel: Roles/Permissions in QuickAdminPanel 2019. 18,701 views18K [07: 27] Adding a Duration: 10:14 Posted: Apr 28, 2019 Laravel 7: Implementing Access Control Using Permissions and Roles – Part 1. One of the most common features in any web application is authentication, which allows a user to log in or log out of an application to securely manage their information. However, many a time an application might also want to restrict access to certain aspects of the system to a certain type of users.

Comments
  • I think you should look at policies. For the whole group and role part, maybe use some reusable code as a trait or base class.
  • The role and group part have the same behaviour, but the precedence is: user specific permissions (most important), group permissions and role permissions (lessest important). I was thinking that i can generate the permission list adding the policies list from all the tables (keeping importance)
  • Hi. How can i define actions dinamically? all the actions (abilities) that a user can do are defined in my resource's table. But the resolvePolicyCallback method in the file Illuminate\Auth\Access\Gate calls the declared functions in the policy file, so i cant define functions dinamically in the ResourcePolicy class :(
  • Can you also provide the relationship from users model? hasRole() is missing currently.
  • @SarojShrestha I've updated it to include the relationship and assignRole() and hasRole() methods in the User model.
  • Great answer, please update to Laravel 7, in order to not lost relevancy
  • @kerrin so shouldn't it be $user->hasRole not $this->hasRole