Finally, in this course, we will take care of permission. In this lesson, we will take care of the back-end part. We will create Role
and Permission
models. Then we will add two roles: admin
and editor
. The admin
role will be able to do everything and the editor
will not be able to delete the posts.
First, we will create the models with migrations for roles and permissions.
php artisan make:model Role -mphp artisan make:model Permission -m
database/migrations/xxxx_create_roles_table.php:
public function up(): void{ Schema::create('roles', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); });}
app/Models/Role.php:
class Role extends Model{ protected $fillable = ['name'];}
database/migrations/xxxx_create_permissions_table.php:
public function up(): void{ Schema::create('permissions', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); });}
app/Models/Permission.php:
class permissions extends Model{ protected $fillable = ['name'];}
Next, we need to create a pivot table for the many-to-many relationship and add relations to the models.
php artisan make:migration "create permission role table"
database/migrations/xxxx_create_permission_role_table.php:
public function up(): void{ Schema::create('permission_role', function (Blueprint $table) { $table->foreignId('permission_id')->constrained(); $table->foreignId('role_id')->constrained(); });}
app/Models/Role.php:
use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Role extends Model{ protected $fillable = ['name']; public function permissions(): BelongsToMany { return $this->belongsToMany(Permission::class); } }
php artisan make:migration "create role user table"
database/migrations/xxxx_create_role_user_table.php:
public function up(): void{ Schema::create('role_user', function (Blueprint $table) { $table->foreignId('role_id')->constrained(); $table->foreignId('user_id')->constrained(); });}
app/Models/User.php:
use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Authenticatable{ // ... public function roles(): BelongsToMany { return $this->belongsToMany(Role::class); }}
Next, we will create seeders for roles with permissions and attach them.
php artisan make:seeder PermissionSeederphp artisan make:seeder RoleSeederphp artisan make:seeder UserSeeder
database/seeders/PermissionSeeder.php:
class PermissionSeeder extends Seeder{ public function run(): void { Permission::create(['name' => 'posts.create']); Permission::create(['name' => 'posts.update']); Permission::create(['name' => 'posts.delete']); }}
database/seeders/RoleSeeder.php:
class RoleSeeder extends Seeder{ public function run(): void { $admin = Role::create(['name' => 'Administrator']); $admin->permissions()->attach(Permission::pluck('id')); $editor = Role::create(['name' => 'Editor']); $editor->permissions()->attach( Permission::where('name', '!=', 'posts.delete')->pluck('id') ); }}
database/seeders/UserSeeder.php:
class UserSeeder extends Seeder{ public function run(): void { $admin = User::factory()->create(['email' => 'admin@admin.com']); $admin->roles()->attach(Role::where('name', 'Administrator')->value('id')); $editor = User::factory()->create(['email' => 'editor@edit.com']); $editor->roles()->attach(Role::where('name', 'Editor')->value('id')); }}
And lastly, we need to call them in the main DatabaseSeeder.
database/seeders/DatabaseSeeder.php:
class DatabaseSeeder extends Seeder{ public function run(): void { $this->call([ PermissionSeeder::class, RoleSeeder::class, UserSeeder::class, ]); }}
Run the migrations and seed the DB.
php artisan migrate --seed
Next, we need to register every permission to the Gate. This can be done in the AppServiceProvider
.
app/Providers/AppServiceProvider.php:
use App\Models\Permission;use Illuminate\Support\Facades\Gate;use Illuminate\Database\QueryException;use Illuminate\Database\Eloquent\Builder; class AuthServiceProvider extends ServiceProvider{ // ... public function boot(): void { try { foreach (Permission::pluck('name') as $permission) { Gate::define($permission, function ($user) use ($permission) { return $user->roles()->whereHas('permissions', function (Builder $q) use ($permission) { $q->where('name', $permission); })->exists(); }); } } catch (QueryException $e) { } }}
We define all of the permissions for the gate. The Gate::define
accepts closure where we define what needs to be true
. In our case, we check if any of the user's roles have the permissions.
The try/catch is needed because at first we don't have a permissions
table and even running migrations will result in error message:
Illuminate\Database\QueryException SQLSTATE[42S02]: Base table or view not found: 1146 Table 'project.permissions' doesn't exist (Connection: mysql, SQL: select `name` from `permissions`) at vendor/laravel/framework/src/Illuminate/Database/Connection.php:793 789▕ // If an exception occurs when attempting to run a query, we'll format the error 790▕ // message to include the bindings with SQL, which will make this exception a 791▕ // lot more helpful to the developer instead of just the database's errors. 792▕ catch (Exception $e) { ➜ 793▕ throw new QueryException( 794▕ $this->getName(), $query, $this->prepareBindings($bindings), $e 795▕ ); 796▕ } 797▕ } i A table was not found: You might have forgotten to run your database migrations. https://laravel.com/docs/master/migrations#running-migrations 1 [internal]:0 Illuminate\Foundation\Application::Illuminate\Foundation\{closure}() +13 vendor frames 15 app/Providers/AuthServiceProvider.php:29
Now that we have defined gates we can use them in the Controller.
app/Http/Controllers/Api/PostController.php:
use Illuminate\Support\Facades\Gate; class PostController extends Controller{ // ... public function store(StorePostRequest $request) { Gate::authorize('posts.create'); if ($request->hasFile('thumbnail')) { $filename = $request->file('thumbnail')->getClientOriginalName(); info($filename); } $post = Post::create($request->validated()); return new PostResource($post); } public function show(Post $post) { Gate::authorize('posts.update'); return new PostResource($post); } public function update(Post $post, StorePostRequest $request) { Gate::authorize('posts.update'); $post->update($request->validated()); return new PostResource($post); } public function destroy(Post $post) { Gate::authorize('posts.delete'); $post->delete(); return response()->noContent(); }}
Now if you try to delete a post with the user which has editor
role, you would get an error alert.
And in the developer console, in the network tab, you would see the expected result from the response This action is unauthorized
.