Laravel — Roles — Permissions

Permissions in your Laravel API

Introduction

Malik Sharfo
7 min readDec 21, 2021

--

Welcome to part six. If you have not read any of the previous parts we do highly recommend that you go back and read those, starting here.

A link to the github repository used, will be provided at the end of this article.

Laravel Policies

Laravel provides a way to authorize actions of users against given resources. For example, even though we authenticate as user, and this user can perform action A, it is not given that this user can also perform action Band C. And vice versa if a user can perform action B and C it is not given that the user can perform action A.

Laravel helps you by offering its authorization features to provide an easy, organized way of managing these types of authorization checks.

Laravel provides us two ways of authorizing actions, and those two are gates and policies. These two can be thought of like routes and controllers. Since we will be using policies we will not go into details about gates. You can view gates as a simple, closure-based approach to authorize.

Policies works like controllers, in that essence that they group logic around a particular model or resource.

You should use policies whenever you wish to authorize an action for a particular model or resource.

Our application is based around user actions, so since we have an App\Models\User model we can have a corresponding App\Policies\UserPolicy to authorize user actions.

You can generate a policy by running the Artisan command in your terminal of choice, for making a policy, as this: php artisan make:policy UserPolicy.

After the policy class has been generated, it needs to be registered. By registering policies we can inform Laravel which policy to use when authorizing actions against a given model type.

The App\Providers\AuthServiceProvider, which is included with a fresh Laravel application, contains a policies property which maps your Eloquent models to their corresponding policies

Registering a policy will instruct Laravel which policy to utilize when authorizing actions against a given Eloquent model, and in our case it is done as following:

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\User;
use App\Policies\UserPolicy;

class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
User::class => UserPolicy::class,
];

...
}

Laravel can automatically discover policies as long as the model and policy follow the standard Laravel naming conventions, instead of manually registering model policies.

Policies must specifically be in a Policies directory either at the directory that contains your models, or above it.

So, for example, the models may be placed in the app/Models directory while the policies may be placed in the app/Policies directory. In such a situation Laravel will firstly check for policies in app/Models/Policies then app/Policies. On top of that, the policy name must match the model name and have a Policy suffix. In our case the policy is placed inside app/Policies.

Authorizing actions using policies via the UserForm class

The User model that is included with a Laravel application includes two methods which are very helpful for authorizing actions. These two methods are the can and cannot.

Both the can and cannot methods receives the name of the action you wish to authorize and the relevant model.

If you have registered a policy for a given model, the can method will automatically call that appropriate policy and return the boolean result, and if no policy is registered, the can method will try and attempt a call to the closure-based Gate that matches the given action name. In our case we will make our own policy for that.

What is spatie/laravel-permission and getting started with Spatie

Spatie is a package that allows you to manage user permissions and roles in a database.

You can install the Spatie package by entering the following inside your prefered terminal that points to your project folder. The package we are going to install is the package that can be used with Laravel v.6.0 >.

composer require spatie/laravel-permission

This installation package will publish a config/permission.php file, make sure to either remove or rename that file if you already have it before installation. This is per Spatie’s own advice.

Follow the installation process as described here.

After following the installation process as described by Spatie themselves, we now need to add the Spatie\Permission\Traits\HasRoles trait to our User model.

...
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
use ..., HasRoles;

...
}

This package makes it so, that users can be associated with permissions and roles. Every role is associated with multiple permissions. A Role and a Permission are regular Eloquent models. They require a name.

Seeding the role(s), permission(s) and assigning them

Under the path Database\Seeders we have created a new file that will be our seeder, PermissionsDemoSeeder:

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
use App\Models\User;

class PermissionsDemoSeeder extends Seeder
{
/**
* Create the initial roles and permissions.
*
* @return void
*/
public function run()
{
app()[PermissionRegistrar::class]->forgetCachedPermissions();

$createPermissionOnUser = Permission::create(['name' => 'Create a new user']);
$updatePermissionOnUser = Permission::create(['name' => 'Update an existing user']);
$readPermissionOnUser = Permission::create(['name' => 'Get an existing user or users']);
$deletePermissionOnUser = Permission::create(['name' => 'Delete or remove a user or users']);

$allroundRole = Role::create(['name' => 'canCRUDonUser']);
$allroundRole->givePermissionTo([
$createPermissionOnUser,
$updatePermissionOnUser,
$readPermissionOnUser,
$deletePermissionOnUser
]);

$adminUser = User::factory()->create([
'name' => 'Admin',
'email' => 'admin@example.com',
'password' => bcrypt("asd")
]);
$adminUser->assignRole($allroundRole);
$adminUser->givePermissionTo([
$createPermissionOnUser,
$updatePermissionOnUser,
$readPermissionOnUser,
$deletePermissionOnUser
]);

$nonAdminUser = User::factory()->create([
'name' => 'Non-Admin',
'email' => 'nonAdmin@example.com',
'password' => bcrypt("asd")
]);
}
}

In this file we have also created two users, one being and admin which is assigned a role with permissions, and the other being an ordinary user without roles and permissions. These will be used for test purposes, so do not pay them much attention, but rather focus on the idea behind them.

In our case, when seeding this file we also empty our entire database tables, therefore when seeding we make sure we have these two users to work with. The Artisan command we run is the below:

php artisan migrate:fresh --seed --seeder=PermissionsDemoSeeder

Next step towards connecting/binding the spatie permissions and Laravel policies

As mentioned earlier, we use Policies whenever we wish to authorize an action for a particular model or resource, in our case a model, User.

For a single authorization “rule” we could define either a Gate or a Policy, the choice between one or the other only defines where the rule will be stored in our code. For Laravel it will be the same thing in the end because in the AuthServiceProvider.php we will “register” the policies:

By having our UserPolicy class, and registered that correct, we have then instructed Laravel to utilize that policy when authorizing an action against our User model.

Since Laravel provides Policies to improve code readability, it is then an “easier” solution to work with Policies, because if an app contains a lot of authorization gates then one can, and might as well get lost in the AuthServiceProvider.php file.

Since our User model already contains two helpful methods, can and cannot, as a part of Laravel application, we can in our case use the can method.

This can method will receive the name of our action, which we have defined inside our UserPolicy class.

UserPolicy:

namespace App\Policies;

use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class UserPolicy
{
use HandlesAuthorization;

/**
* Create a new policy instance.
*
* @return void
*/
public function __construct()
{
//
}

public function create(User $user)
{
return $user->hasRole('canCRUDonUser');
}

public function update(User $user)
{
return $user->hasRole('canCRUDonUser');
}
}

The functions create and update follows the Laravel naming conventions, and these two receives our User object which we want to authorize against. When working with spatie permissions and roles, these includes helper methods, such as the one we have used, hasRole. The hasRole method checks whether the $user has the role named canCRUDonUser, and returns a boolean. This role, canCRUDonUser, we have created with our PermissionsDemoSeeder and stored inside the roles database table.

Inside UserForm we modify our authorize method, to check whether the user in action, has access to those given roles, or some of them.

UserForm:

...

class UserForm extends FormRequest {

public function authorize()
{
if($this->isMethod('post'))
return $this->user()->can('create', User::class);
return $this->user()->can('update', User::class);
}

...
}

The authorize method is usually used to authorize the actual request basing on some policy we, or you, would like to respect. In our case we are trying to make a request to either create or update a User model. We use the authorize method to check that the specific user, who is either trying to create or update, has the permissions to do so.

We first check which type of request that is in action, and then use the can method on our user in action, and see if this user “can” perform either of these two actions, create and update, on the User model. The authorized role that gets returned, depends on the form request action.

The can method, in simple terms, receives the name of the action we wish to authorize and the relevant model. This method will automatically call the appropriate policy, UserPolicy, and returns the boolean result of the action that is in question.

Testing

In this article, no tests have been made, to check the results of the new functionalities implemented and modified. But do not worry about that, we will do that in the next article, so stay tuned for that.

Changes and implementations along the way

  • Inside UserController new annotations have been added to verify our OpenAPI, that now uses authorization against authenticated users.
...class UserController extends Controller
{
/**
* ...
* @OA\Response(response=403, description="Forbidden"),
* ...
*/
public function index(Request $request)
{
return User::all();
}
/**
* ...
* @OA\Response(response=403, description="Forbidden"),
* ...
*/
public function store(Form $request)
{
return $request->saveOrUpdate();
}
/**
* ...
* @OA\Response(response=403, description="Forbidden"),
* ...
*/
public function update(Form $request, int $id)
{
return $request->saveOrUpdate($id);
}
}

Github repository for this article can be found here.

--

--