How To Handle Authorization in Laravel The Right Way
Tags: laravel, php, tutorial
Published: 2022-08-22 11:49:00

Laravel's [authorization module](https://laravel.com/docs/9.x/authorization) gives you the skeleton to implement robust authorization logic in your app, with two choices: Gates and Policies.

[Spatie Laravel-Permission](https://spatie.be/docs/laravel-permission/v5/introduction) offers a way to implement roles and permissions in your app.

Both are fully documented with examples, but when you try to apply it, everything just gets messy.

Here's how I implement authorization in my Laravel projects.

## Background and preconditions

For this technique/pattern or whatever you want to call it to work. I follow a set of conventions to make it easier.

### 1. Models

I don't use modules/services/repositories or any other shenanigans coming up every day. All my models are in the default `app/models` directory, and they all extend Eloquent's `Model`.

### 2. Routes

For routing whenever possible I use resource routes. For example, say I'm building an e-commerce store.

```php
Route::resource('products', ProductController::class);
```

### 3. Controllers

My controllers are straight defaults from Laravel and all live in the `app/controllers` directory. I might namespace them depending on what they are for. And again, I use resourceful controllers.

>Just stick to the defaults.

The logic behind how I authorize goes like this:

For every app, there's are going to be models, CRUD and users. Each CRUD action performed on a model has to be authorized by a single permission. For example, to create a product, a user should have permission to `create products`.

Laravel offers a nice way to do just that, with polices. 
Policies authorize the whole CRUD operations and pairs nicely with resourceful controllers, as you'll see by the end of this article.

Each method should return a boolean to see if a user can perform a certain CRUD action, We then just check if the user has permission to that and return that boolean. Easy-peasy.

Now, without enough theory, let's see how this is done.

## Requirements

To implement this, you are going to need the following

*   A Laravel app, best if it's a complete app that just needs authorization

*   `spatie/laravel-permissions` To handle roles and permissions

## Setup

Let's install the required packages. In your terminal, install `spatie/laravel-permissions` with composer.

```bash
composer require spatie/laravel-permissions
```

Follow the documentation to set up the package.

Now we are ready to do the authorization. But first we need to…

## Open the Gates for super admin

For every app, there has to be a `super admin` or root user with all the rights and can do anything. This is the first thing I would implement. To do this, we need to use `Gate::before()` method to allow a user to do anything as long as they have the `super admin` role.

In your `AuthServiceProvider` add this to the boot method after registering polices.

```php
Gate::before(function ($user, $ability) {  
    return $user->hasRole('super admin') ? true : null;  
});
```

This will make sure that every time `$user->can()` is called, it returns true as long as the user has `super admin` role.

## Generate roles and permissions

Now that's done, let's go ahead and create the super admin role. But first we need more preparation. See, normally you'd add authorization after your application is finished, and you know everything works as it's supposed to. At least, that's how I'd do it.

To make sure we can just plug and play this into production, let's create a seeder.

```bash
php artisan make:migration RolesAndPermissions
```

In the seeder I will use the following code

```php
use Illuminate\Database\Eloquent\Model;  
use Illuminate\Database\Seeder;  
use Illuminate\Support\Facades\File;  
use Illuminate\Support\Str;  
use ReflectionClass;  
use Spatie\Permission\Models\Role;  
use Spatie\Permission\Models\Permission;  
use Symfony\Component\Finder\SplFileInfo;

class RolesPermissions extends Seeder  
{  
    /**  
     * Run the database seeds.     
     *     
     * @return void  
     */  
    public function run()  
    {  
        $roles = collect([  
            'super admin',  
        ]);  
  
        $models = collect(File::allFiles(app_path()))  
            ->map(function (SplFileInfo $info) {  
                $path = $info->getRelativePathname();  
  
                $class = sprintf('\%s%s',  
                app()->getNamespace(),  
                Str::replace('/', '\\', Str::beforeLast($path, '.')));  
  
                return $class;  
  
            })  
            ->filter(function (string $class) {  
                try {  
                    $reflection = new ReflectionClass($class);  
                } catch (\ReflectionException $throwable) {  
                    return false;  
                }  
  
                return $reflection->isSubclassOf(Model::class) && 
	            !$reflection >isAbstract();  
            })  
            ->map(function ($model) {  
                return Str::lower(Str::plural(Str::afterLast($model, '\\')));  
            })  
            ->map(function ($model) {  
                return [  
                    "create {$model}",  
                    "update {$model}",  
                    "delete {$model}",  
                    "restore {$model}",  
                    "view {$model}",  
                    "force delete {$model}",  
                ];  
            });  
  
  
        $roles->each(function ($role) use ($models) {  
            $role = Role::create(['name' => $role]);  
        });  
    }  
}
```

Okay, let me explain…

To authorize this, we just require permissions for each CRUD action per model. For example, there is a permission to `create users` or `update users` which we can use as `$user->can('update users')`

Notice there is only one role `super admin` defined. All other roles will be created  and assigned permissions via a GUI by the super admin.

In the first part of the code, I get all the classes in the `app` directory as their fully qualified class names, e.g., `\App\Models\User`.

Next, we use the `Reflection API` to make sure the class is insatiable, and it extends Eloquent's base `Model`.

Next, I map that and return just a plural lower case model name like `users`.
Map that again to return an array of permission for each model like `create users`, `view users`, `update users`, or `delete users` for example.

Then we just go on to create the default roles which is just `super admin`, but you're welcome to add more if you want like `editor`, `seo consultant`, `intern` or something.

Let's go ahead, seed the database

```bash
php artisan db:seed RolesAndPermissions
```

Now let's create the policies for each of our models we have.

## Defining the policies

Let's take for example we are building an e-commerce store, we're going to generate the policy for the Product model

```sh
php artisan make:policy ProductPolicy --model=Product
```

This will generate a boilerplate policy in your `app/policies` directory. This is how I would define my policy.

```php
namespace App\Policies;  
  
use App\Models\Product;  
use App\Models\User;  
use Illuminate\Auth\Access\HandlesAuthorization;  
  
class ProductPolicy  
{  
    use HandlesAuthorization;  
  
    /**  
     * Determine whether the user can view any models.     
     *     
     * @param \App\Models\User  $user  
     */  
    public function viewAny(User $user): bool  
    {  
        return $user->can('view all products');  
    }  
  
    /**  
     * Determine whether the user can view the model.     
     *     
     * @param \App\Models\User  $user  
     * @param \App\Models\Product  $product  
     */  
    public function view(User $user, Product $product): bool  
    {  
        return $user->can('view products');  
    }  
  
    /**  
     * Determine whether the user can create models.     
     *     
     * @param \App\Models\User  $user  
     */  
    public function create(User $user): bool  
    {  
        return $user->can('create products');  
    }  
  
    /**  
     * Determine whether the user can update the model.     
     *     
     * @param \App\Models\User  $user  
     * @param \App\Models\Product  $product  
     */  
    public function update(User $user, Product $product): bool  
    {  
        return $user->can('update products');  
    }  
  
    /**  
     * Determine whether the user can delete the model.     
     *     
     * @param \App\Models\User  $user  
     * @param \App\Models\Product  $product  
     */  
    public function delete(User $user, Product $product): bool  
    {  
        return $user->can('delete products');  
    }  
  
    /**  
     * Determine whether the user can restore the model.     
     *     
     * @param \App\Models\User  $user  
     * @param \App\Models\Product  $product  
     */  
    public function restore(User $user, Product $product): bool  
    {  
        return $user->can('restore products');  
    }  
  
    /**  
     * Determine whether the user can permanently delete the model.     
     *     
     * @param \App\Models\User  $user  
     * @param \App\Models\Product  $product  
     */  
    public function forceDelete(User $user, Product $product): bool  
    {  
        return $user->can('force delete products');  
    }  
}
```

Take for example the `update()` method, I just check if the user can `update products`. This will return true if the user has permission to `update products` otherwise it returns false.

## Authorizing controllers

For each CRUD authorization method, we just check if the user has permission to perform that action. Since we are using resourceful controllers. Our product controller can look like this.

```php
/**  
 * Create the controller instance. 
 * 
 * @return void  
 */  
public function __construct()  
{  
    $this->authorizeResource(Product::class, 'product');  
}
```

Calling the `authorizeResource()` method will authorize all the resourceful routes using that Product Policy we just created, simple and powerful.

Just like that you have a robust authorization logic with strong role based access control. At this point I would typically give a set of permissions to a role and give that role to a user.
And with that the user gets the permission. I would use a GUI so that I can change permissions any time.

## Authorization in just one artisan command
So as I was writing this article, I realized that I've been re implementing this over and over again. I decided to create a [package](https://github.com/flixtechs-labs/laravel-authorizer) that helps you generate all that in just one command.

### How to use this package
Getting started is simple. You just need to install the package via composer

```sh
composer require flixtechs-labs/laravel-authorizer
```

Next run the following command to setup the package

```sh
php artisan authorizer:setup
```

After that generate your supercharged policy with

```sh
php artisan authorizer:policies:generate Product --model=Product
```

Go to the [documentation](https://github.com/flixtechs-labs/laravel-authorizer) to see how this package can speed up your development time.


## Final thoughts

Handling [authorization](https://laravel.com/docs/9.x/authorization) in Laravel can be quite overwhelming especially when you're starting out. I hope this article gives you a great starting to start using [authorization](https://laravel.com/docs/9.x/authorization) in your Laravel apps.