Featured image of post Permission Control via Routing in Laravel (Not Limited to Laravel, Just a Conceptual Approach)

Permission Control via Routing in Laravel (Not Limited to Laravel, Just a Conceptual Approach)

Permission management is a module that can be both simple and complex depending on implementation.

Start

Permission design is a crucial feature for backend management systems. PHP already has many mature packages for this purpose, so we don’t need to reinvent the wheel (though you certainly can if preferred).


PS

Here are two common historical approaches for permission verification:

  1. Verify required permissions individually on each page
  2. Centralized verification via middleware

First, let’s examine a simplified database schema (retaining only critical fields) with its ER diagram:

(Note: In this design, users don’t directly possess permissions but inherit them through roles. Many packages allow direct user permissions.)

Models

1. User Model

<?php

namespace App\Models;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;
    
    /**
     * Relationship with Role model
     */
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
    
    /**
     * Check if user has a specific permission
     * 1. Get required permission name
     * 2. Iterate through user's roles
     * 3. Check if any role has the permission
     */
    public function hasPermission($permissionName)
    {
        foreach ($this->roles as $role) {
            if ($role->permissions()->where('name', $permissionName)->exists()) {
                return true;
            }
        }
        return false;
    }
}

2. Role Model

<?php

namespace App\Models;

class Role extends Model
{
    public function users()
    {
        return $this->belongsToMany(User::class);
    }
    
    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }
}

3. Permission Model

<?php

namespace App\Models;

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

Database Seeding

Sample data:

# Users
+----+-------+----------+
| id | name  | password |
+----+-------+----------+
| 1  | gps   | 123456   |
| 2  | david | 123456   |

# Roles
+----+-------+
| id | name  |
+----+-------+
| 1  | admin |

# Permissions
+----+-----------------+
| id | name            |
+----+-----------------+
| 1  | create_product  |
| 2  | delete_product  |

# role_user (User gps has admin role)
+---------+---------+
| role_id | user_id |
+---------+---------+
| 1       | 1       |

# permission_role (Admin role has product permissions)
+---------+---------------+
| role_id | permission_id |
+---------+---------------+
| 1       | 1             |
| 1       | 2             |

Approach 1: Page-Level Verification

<?php

namespace App\Http\Controllers;

use App\Models\Product;

class ProductsController extends Controller
{
    public function store(Request $request)
    {
        if (!$request->user()->hasPermission('create_product')) {
            abort(403);
        }
        // Business logic
        return back()->with('status', 'Product created');
    }

    public function destroy(Product $product)
    {
        if (!$request->user()->hasPermission('delete_product')) {
            abort(403);
        }
        // Business logic
        return back()->with('status', 'Product deleted');
    }
}

Approach 2: Centralized Middleware Verification

Enhance the permissions table with a route column:

# permissions
+----+-----------------+------------------+
| id | name            | route            |
+----+-----------------+------------------+
| 1  | create_product  | products.store   |
| 2  | delete_product  | products.destroy |

Create a middleware:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Route;
use App\Models\Permission;

class PermissionAuth
{
    public function handle($request, Closure $next)
    {
        $route = Route::currentRouteName();
        
        if ($permission = Permission::where('route', $route)->first()) {
            if (!auth()->user()->hasPermission($permission->name)) {
                return response()->view('errors.403', [
                    'status' => "Insufficient permissions. Required: {$permission->name}"
                ]);
            }
        }

        return $next($request);
    }
}

END

For Laravel implementations, consider using the mature package:
https://github.com/spatie/laravel-permission