Laravel Permission - Managing user roles and permissions is a crucial aspect of building secure and scalable web applications. Whether you're developing an e-commerce platform, a content management system, or a SaaS solution, defining what users can and cannot do ensures both functionality and security. Laravel, known for its elegant syntax and robust ecosystem, provides excellent tools to streamline this process.
One such tool is the Laravel Permission package, a flexible and powerful solution that simplifies handling user roles and permissions. This package allows developers to implement granular access control, offering the ability to assign roles, define permissions, and enforce them across various parts of the application with ease.
In this blog, we’ll guide you step-by-step on how to set up and use the Laravel Permission package to create advanced user role hierarchies. From installation to best practices, you'll learn how to harness its capabilities to build a secure, efficient, and well-structured application. Let’s dive in and elevate your Laravel projects with sophisticated permission management!
Getting Started
In this article, I will try to explain everything in as much detail as possible, from installing Laravel and the required packages to implementing permissions using blade directives and middleware.
Install Fresh Laravel Project
Ok, let's start by installing a fresh Laravel project.
laravel new laravel-permission
Please install Laravel using the Laravel Installer as per the command above, or you can also use other methods as explained in the Laravel documentation.
cd laravel-permission
If the installation process is successful, let's enter the project directory using the command as in the example above.
Install Laravel UI
In this tutorial, we will need an authentication system. I will use the Laravel UI package to create a simple authentication system using Bootstrap.
composer require laravel/ui
php artisan ui bootstrap --auth
npm install && npm run dev
It's very easy to install the Laravel UI package. Please run the commands above sequentially on your terminal.
User Management
Okay, we already have an authentication system in our Laravel project. Next, let's create an admin user using Seeder.
php artisan make:seeder UserSeeder
Run the artisan command as above to create the UserSeeder class.
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$user = User::create([
'name' => 'Admin',
'email' => 'admin@gmail.com',
'password' => bcrypt('password')
]);
}
}
If the UserSeeder class has been successfully created, open the file and add the script as above. So, with this class, we will create an Admin user.
php artisan db:seed --class=UserSeeder
Run the command as above to execute the UserSeeder class. If so, then we have an Admin user account for this project.
Okay, next, let's create user management. This will allow us to create and update user details such as name, email, and role.
php artisan make:controller UserController
Please run the artisan command as above to create a UserController file.
routes/web.php
Route::resource('users', App\Http\Controllers\UserController::class);
Open the routes/web.php
file, then add route resources that we direct to the UserController.
Let's create a UserRequest class to handle validation on UserController.
php artisan make:request UserRequest
Please run the artisan command as above to create the Request file.
UserRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required',
'email' => 'required|email|unique:users,email,' . $this->user->id,
];
}
}
Open the UserRequest file we successfully generated earlier. Then, add variables like the example above to the function rules
. Here, we will only validate the name
and email
input.
Okay, next, let's work on the UserController.
UserController.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\UserRequest;
class UserController extends Controller
{
public function index()
{
return view('users.index',[
'users' => User::paginate(10)->withQueryString()
]);
}
public function create()
{
return view('users.form',[
'user' => new User(),
'submit' => 'Create',
'action' => route('users.store'),
'method' => 'POST',
'title' => 'Create User'
]);
}
public function store(UserRequest $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt('password')
]);
return to_route('users.index')->with('success', 'User created successfully');
}
public function edit(User $user)
{
return view('users.form',[
'user' => $user,
'submit' => 'Update',
'action' => route('users.update', $user),
'method' => 'PUT',
'title' => 'Edit User'
]);
}
public function update(UserRequest $request, User $user)
{
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
return to_route('users.index')->with('success', 'User updated successfully');
}
}
I will convey this step's very basic information. Here, we will create an index method to return data from the User model and direct it to the users/index.blade.php
view. I will also explain the use of reusable forms for our case study this time. We will only use one and the same view file for the create and edit forms.
Create Dynamic Title
Before we create a view for user management, let's make a dynamic title.
layouts/app.blade.php
Open the app.blade.php
file, look for the code as below.
<title>{{ config('app.name', 'Laravel') }}</title>
Please change it to be like the code below.
<title>
@hasSection('title')
@yield('title') - {{ config('app.name', 'Laravel') }}
@else
{{ config('app.name', 'Laravel') }}
@endif
</title>
With the code above, if we add @section('title') to the rendered view, the title that will be displayed is the section's value.
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
And next, let's add the Users menu. Find the code as above, then add a new menu like the example code below.
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ route('users.index') }}">{{ __('Users') }}</a>
</li>
</ul>
That way, we will have a new menu on the navbar.
Okay, next, let's create a new folder inside the resources/views
folder named users and create an index.blade.php
file inside that folder.
users/index.blade.php
@extends('layouts.app')
@section('title', 'Users')
@section('content')
<div class="container">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<h1 class="fs-5">Users</h1>
<a href="{{ route('users.create') }}" class="btn btn-sm btn-primary">Create</a>
</div>
<div class="card-body">
@if (session('success'))
<div class="alert alert-success" role="alert">
{{ session('success') }}
</div>
@endif
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Roles</th>
<th scope="col" class="text-end">Action</th>
</tr>
</thead>
<tbody>
@forelse ($users as $user)
<tr>
<th scope="row">{{ $user->id }}</th>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>-</td>
<td class="text-end">
<a href="{{ route('users.edit', $user->id) }}" class="btn btn-sm btn-primary">Edit</a>
</td>
@empty
<td colspan="5">No users found</td>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
Add the above code to the index.blade.php
file we just created. This is a simple view, containing a users table and a create button to create a new user.
users/form.blade.php
@extends('layouts.app')
@section('title',"{$title}")
@section('content')
<div class="container">
<div class="row d-flex justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<form action="{{ $action }}" method="post">
@if($method == 'PUT')
@method('PUT')
@endif
@csrf
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name') ?? $user->name }}">
@error('name')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control @error('email') is-invalid @enderror" id="email" name="email" value="{{ old('email') ?? $user->email }}">
@error('email')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">
{{ $submit }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Next, create a form.blade.php
file inside the users folder and add the code above. In this form, we still only have the name and email input fields. We will add the role input field later.
Let's try the user management. Run the project with the php artisan server
command, then login using the account we created using the seeder earlier.
The following is the user management view that we have created.
OK, up to this stage, we have successfully created user management.
Roles and Permission Management
We have arrived at the core explanation. At this stage, I will explain everything from installing the Laravel permissions package to creating role management and using Laravel permissions in basic usage.
Install and Setup Laravel Permissions Package
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
Please install Laravel permissions package using the command as above in sequence.
models/User.php
use Spatie\Permission\Traits\HasRoles;
...
...
use HasRoles;
Open the User model file and add the HasRoles
trait as in the example above.
php artisan make:seeder RoleSeeder
php artisan make:seeder PermissionSeeder
Create RoleSeeder and PermissionSeeder files by running the artisan commands as above.
RoleSeeder
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class RoleSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$roles = [
[
'name' => 'admin',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'editor',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
]
];
DB::table('roles')->insert($roles);
$admin = User::where('email', 'admin@gmail.com')->first();
if ($admin) {
$admin->assignRole('admin');
}
}
}
Open the RoleSeeder.php
file and change the code in it to be like the example above. Here, we will create admin and editor roles and apply the admin role to the admin user.
php artisan db:seed --class=RoleSeeder
Execute the RoleSeeder
file by running the seeder command as above.
PermissionSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Spatie\Permission\Models\Role;
class PermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$permissions = [
[
'name' => 'user table',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'create user',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'edit user',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'delete user',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'post table',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'create post',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'edit post',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'delete post',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'role table',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
],
[
'name' => 'edit role',
'guard_name' => 'web',
'created_at' => now(),
'updated_at' => now()
]
];
DB::table('permissions')->insert($permissions);
$adminRole = Role::where('name', 'admin')->first();
if ($adminRole) {
$permissions = DB::table('permissions')->pluck('id')->toArray();
$adminRole->syncPermissions($permissions);
}
}
}
Next, open the PermissionSeeder
file and change its code to be like the example above. Here, we will create permissions and apply all of those permissions to the admin role.
php artisan db:seed --class=PermissionSeeder
Execute the PermissionSeeder file using the artisan command as in the example above.
Let's update the users/index.blade.php
view file.
Open users/index.blade.php, look for the code as below.
<td>-</td>
Then, change it to be like below.
<td>
@forelse($user->roles as $role)
<span class="badge bg-dark">{{ $role->name }}</span>
@empty
-
@endforelse
</td>
With the code above, we will display the roles owned by the user.
Next, let's adjust the code we created in UserController. Change the code below.
public function create()
{
return view('users.form',[
'user' => new User(),
'submit' => 'Create',
'action' => route('users.store'),
'method' => 'POST',
'title' => 'Create User'
]);
}
Please change it to something like the one below.
public function create()
{
return view('users.form',[
'user' => new User(),
'submit' => 'Create',
'action' => route('users.store'),
'method' => 'POST',
'title' => 'Create User',
'roles' => Role::get()
]);
}
Here, we add the roles
variable to get roles data from the Role model.
public function store(UserRequest $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt('password')
]);
return to_route('users.index')->with('success', 'User created successfully');
}
Then, move on to the other method. Change the store
method to the one below.
public function store(UserRequest $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt('password')
]);
$user->assignRole($request->role);
return to_route('users.index')->with('success', 'User created successfully');
}
Here we add the script $user->assignRole($request->role);
to save the role that the user has selected.
public function edit(User $user)
{
return view('users.form',[
'user' => $user,
'submit' => 'Update',
'action' => route('users.update', $user),
'method' => 'PUT',
'title' => 'Edit User'
]);
}
Then, in the edit
method, change it to something like below.
public function edit(User $user)
{
return view('users.form',[
'user' => $user,
'submit' => 'Update',
'action' => route('users.update', $user),
'method' => 'PUT',
'title' => 'Edit User',
'roles' => Role::get()
]);
}
The edit
method is the same. Here, we add the roles variable to get role data from the Role model.
public function update(UserRequest $request, User $user)
{
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
return to_route('users.index')->with('success', 'User updated successfully');
}
Next, adjust the update
method to be as below.
public function update(UserRequest $request, User $user)
{
$user->update([
'name' => $request->name,
'email' => $request->email,
]);
$user->syncRoles($request->role);
return to_route('users.index')->with('success', 'User updated successfully');
}
Here, we add $user->syncRoles($request->role);
to synchronize the roles on that user.
use Spatie\Permission\Models\Role;
Don't forget to import the Role
model in UserController.php
users/form.blade.php
<div class="mb-3">
<label for="role" class="form-label">Role</label>
<select class="form-select @error('role') is-invalid @enderror" id="role" name="role">
<option value="">-- Select Role --</option>
@foreach($roles as $role)
<option value="{{ $role->name }}" {{ in_array($role->name, $user->roles->pluck('name')->toArray()) ? 'selected' : '' }}>{{ $role->name }}</option>
@endforeach
</select>
@error('role')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
Next, open the users/form.blade.php
file, then add a select field like the code above.
Role Management
Ok, we will create role management to manage the permissions of each role.
php artisan make:controller RoleController
Let's create a RoleController. Please run the command as above to create a RoleController file.
RoleController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RoleController extends Controller
{
public function index()
{
return view('roles.index',[
'roles' => Role::paginate(10)->withQueryString()
]);
}
public function edit(Role $role)
{
return view('roles.form',[
'role' => $role,
'permissions' => Permission::get(),
]);
}
public function update(Request $request, Role $role)
{
$role->syncPermissions($request->permissions);
return to_route('roles.index')->with('success', 'Role updated successfully');
}
}
Open the RoleController file that we just created, then adjust the code in it to be like the code above.
routes/web.php
Route::prefix('roles')->group(function () {
Route::get('/', [App\Http\Controllers\RoleController::class, 'index'])->name('roles.index');
Route::get('/{role}/edit', [App\Http\Controllers\RoleController::class, 'edit'])->name('roles.edit');
Route::put('/{role}', [App\Http\Controllers\RoleController::class, 'update'])->name('roles.update');
});
Open routes/web.php
, add a new route like the code above into the web.php file.
roles/index.blade.php
@extends('layouts.app')
@section('title', 'Roles')
@section('content')
<div class="container">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<h1 class="fs-5">Roles</h1>
</div>
<div class="card-body">
@if (session('success'))
<div class="alert alert-success" role="alert">
{{ session('success') }}
</div>
@endif
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Permissions</th>
<th scope="col" class="text-end">Action</th>
</tr>
</thead>
<tbody>
@forelse ($roles as $key => $role)
<tr>
<th scope="row">{{ ++$key }}</th>
<td>{{ $role->name }}</td>
<td>
@forelse($role->permissions as $permission)
<span class="badge bg-dark me-1">{{ $permission->name }}</span>
@empty
-
@endforelse
</td>
<td class="text-end">
<a href="{{ route('roles.edit', $role->id) }}" class="btn btn-sm btn-primary">Edit</a>
</td>
</tr>
@empty
<tr>
<td colspan="4">No roles found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
Create a new folder inside the resources/views folder named roles and create an index.blade.php file inside the roles folder. Please copy the code above and paste it into the new file (index.blade.php).
roles/form.blade.php
@extends('layouts.app')
@section('title',"{$role->name}")
@section('content')
<div class="container">
<div class="row d-flex justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h1 class="fs-5">{{ $role->name }}</h1>
</div>
<div class="card-body">
<form action="{{ route('roles.update',$role->id) }}" method="post">
@method('PUT')
@csrf
<div class="row mb-2">
@foreach ($permissions as $permission)
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="{{ $permission->name }}" id="permission{{ $permission->id }}" name="permissions[]" {{ in_array($permission->id, $role->permissions->pluck('id')->toArray()) ? 'checked' : '' }}>
<label class="form-check-label" for="permission{{ $permission->id }}">
{{ $permission->name }}
</label>
</div>
</div>
@endforeach
</div>
<button type="submit" class="btn btn-primary">
Update
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Then, create a new file named form.blade.php in the roles folder. Please copy the code above and paste it into the new file.
Next, we can add a new menu to the navbar.
<li class="nav-item">
<a class="nav-link" href="{{ route('roles.index') }}">{{ __('Roles') }}</a>
</li>
Open the app.blade.php file, then add the code above to the app.blade.php file. Place it under the Users menu that we added earlier.
Now, let's try role management. Run the project, then open it in a browser. The table and form will display like the image below.
We have successfully created role management up to this point. In the following explanation, let's try to implement it both with a blade directive and with middleware.
Basic Usage
Okay, let's try what we've created.
Blade Directive
First, let's try using the blade directive. Please open the layouts/app.blade.php file.
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
@can('user table')
<li class="nav-item">
<a class="nav-link" href="{{ route('users.index') }}">{{ __('Users') }}</a>
</li>
@endcan
@can('role table')
<li class="nav-item">
<a class="nav-link" href="{{ route('roles.index') }}">{{ __('Roles') }}</a>
</li>
@endcan
@can('post table')
<li class="nav-item">
<a class="nav-link" href="#">{{ __('Posts') }}</a>
</li>
@endcan
</ul>
In the menus section, please change it to be like the code above. With this code, it means that the Users menu can only be accessed by users or roles that have the "user table" permission. And the Roles menu can only be accessed by users or roles that have the "role table" permission. And the Posts menu, although we haven't created the management, we have created the permission, so we can try it by adding the Posts menu with the "post table" permission.
Now, you can try it by setting the permissions of each role (admin and editor). Then, try adding a user with a different role. Suppose you have set the editor role, which only has the following permissions: post table, create post, and edit post, and you add a new user with the editor role. Then, when you try to log in using the editor account, you should only see the Posts menu there.
Middleware
Okay, next, let's try using middleware. Laravel permission can be implemented using middleware in routes and controllers.
bootstrap/app.php
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
Please open the bootstrap/app.php
file, then adjust the withMiddleware
section to be like the example above. Here, we will register the default middleware from the Laravel permission package.
Let's try to implement middleware on routes.
Open the routes/web.php file and look for the code below.
Route::prefix('roles')->group(function () {
Route::get('/', [App\Http\Controllers\RoleController::class, 'index'])->name('roles.index');
Route::get('/{role}/edit', [App\Http\Controllers\RoleController::class, 'edit'])->name('roles.edit');
Route::put('/{role}', [App\Http\Controllers\RoleController::class, 'update'])->name('roles.update');
});
Then add middleware as below.
Route::prefix('roles')->group(function () {
Route::get('/', [App\Http\Controllers\RoleController::class, 'index'])->name('roles.index')->middleware('can:role table');
Route::get('/{role}/edit', [App\Http\Controllers\RoleController::class, 'edit'])->name('roles.edit')->middleware('can:edit role');
Route::put('/{role}', [App\Http\Controllers\RoleController::class, 'update'])->name('roles.update')->middleware('can:edit role');
});
With the code as above, it means that to be able to access /roles
, the user or role must have "role table" permission. And to access the /{role}/edit
route, the user or role must have "edit role" permission.
UserController.php
public function __construct()
{
$this->middleware(['permission:user table'])->only('index');
$this->middleware(['permission:create user'])->only(['create', 'store']);
$this->middleware(['permission:edit user'])->only(['edit', 'update']);
}
And finally, we can implement middleware
on the controller. Please open the UserController.php
file, then add the function __construct()
as in the example above. The code above means that the index function can only be accessed by users or roles that have the "user table" permission. The create and store functions can only be accessed by users or roles that have the "create user" permission. The edit and update functions can only be accessed by users or roles that have the "edit user" permission.
Implementing advanced role and permission management in Laravel has never been easier, thanks to the Laravel Permission package. In this article, we’ve walked through the step-by-step process, starting with setting up a fresh Laravel project, installing the Laravel UI package for basic authentication, installing the Laravel Permission package, and finally implementing permissions using Blade directives and middleware.
By following this guide, you can ensure your application has robust access control while maintaining ease of management and scalability for future needs. A well-structured approach to roles and permissions not only enhances security but also provides flexibility to meet diverse user requirements.
We hope this tutorial helps you build more professional and secure applications. Feel free to experiment and explore the full potential of the Laravel Permission package to suit your project’s needs. Happy coding, and good luck!
Source Code: Laravel Permission
Top comments (0)