Tutorial . Laravel . Security

Build a Role & Permission System in Laravel from Scratch

Joy Dey June 24, 2026 10 min read Laravel Security Authorization Tutorial

When building complex web applications, managing who can access what is paramount. While popular packages like spatie/laravel-permission are fantastic, they can sometimes be overkill or restrictive if you need a highly bespoke solution.

In this guide, we'll architect and build a robust, native Role and Permission authorization system in Laravel from the ground up, utilizing Laravel's built-in Gates and Policies.

Step 1: The Database Structure

We need three main tables (aside from users): roles, permissions, and a pivot table role_permission. A user will belong to a role, and a role will have many permissions.

Generate the migrations:

php artisan make:migration create_roles_table
php artisan make:migration create_permissions_table
php artisan make:migration create_role_permission_table

The schema logic is simple. users gets a role_id foreign key. The pivot table ties role_id and permission_id.

Step 2: Defining the Models

Next, define the Eloquent relationships. A User belongsTo a Role. A Role belongsToMany Permissions.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }
}
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    public function role()
    {
        return $this->belongsTo(Role::class);
    }

    public function hasPermission($permissionSlug)
    {
        return $this->role->permissions->contains('slug', $permissionSlug);
    }
}

The hasPermission helper method on the User model checks if their assigned role contains the given permission slug (e.g., edit_posts).

Step 3: Registering Laravel Gates

Laravel's authorization system resolves around Gates. Instead of manually checking auth()->user()->hasPermission() everywhere, we can hook into the Gate facade to define abilities globally.

Open app/Providers/AuthServiceProvider.php and update the boot method:

use Illuminate\Support\Facades\Gate;
use App\Models\Permission;

public function boot()
{
    $this->registerPolicies();

    try {
        // Fetch all permissions from DB
        Permission::get()->map(function ($permission) {
            Gate::define($permission->slug, function ($user) use ($permission) {
                return $user->hasPermission($permission->slug);
            });
        });
    } catch (\Exception $e) {
        // Handle scenario where DB is not migrated yet
    }
}

This dynamic registration creates a Gate for every permission in your database.

Step 4: Protecting Routes and Views

Now that Gates are registered, you can use Laravel's standard authorization features everywhere.

In Routes:

Route::post('/posts', [PostController::class, 'store'])
     ->middleware('can:create_posts');

In Controllers:

public function destroy(Post $post)
{
    $this->authorize('delete_posts');
    $post->delete();
}

In Blade Views:

@can('edit_posts')
    <a href="/posts/edit">Edit Post</a>
@endcan

Conclusion

Building a custom role and permission system in Laravel is surprisingly straightforward thanks to Eloquent and Gates. By maintaining control over the implementation, you can easily adapt it for multi-tenancy, granular team permissions, or any other complex authorization requirement your application may face.