Некоторые вещи в Laravel, на мой взгляд, сделаны довольно странно. Одна из них – это то, как предлагается реализовывать роли пользователей инструментами фреймворка. В гайде от разработчиков предлагается проверять роль пользователя в middleware: то есть мы сначала авторизуем пользователя, а только потом узнаем, имеет ли он соответствующий доступ. Для проверки каких-то флагов доступа это выглядит разумно, но для ролей – решение странное.
В конце статьи также упоминается про создание ролей с помощью guard’ов, которые как раз и отвечают за авторизацию. И это кажется подходящим решением, но автор статьи не рекомендует их использовать таким образом.
Тем не менее, в сети есть немало примеров, предлагающих решение на основе guard’ов:
- https://pusher.com/tutorials/multiple-authentication-guards-laravel/
- https://webjourney.dev/how-to-implements-multiple-authentication-for-a-website-using-laravel-10-with-guards
- https://github.com/mberecall/Laravel-8-Multi-Guards-Authentication/
Правда все эти примеры имеют фатальный недостаток: пользователи с разными ролями должны находится в разных таблицах. Довольно странно создавать 5 одинаковых таблиц для 5 разных ролей, особенно в случаях, когда роли кардинально не отличаются.
Поэтому я предлагаю другое решение, благо это не сложно сделать, по крайней мере в последних версиях.
Кастомный guard user provider
Функциональность guard делится на driver
и provider
. Последний нам и нужно кастомизировать.
Для начала добавим поле role в таблицу users, где и будут хранится роли.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('role', 100)->after('remember_token')->default('user');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};
Добавляем admin guard. Меняем driver user provider на свой: я назвал его eloquentWithRole
. Поле role в конфиге – это значение, которое будет храниться в базе данных.
<?php
return [
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
'users' => [
'driver' => 'eloquentWithRole',
'model' => App\Models\User::class,
'role' => 'user'
],
'admins' => [
'driver' => 'eloquentWithRole',
'model' => App\Models\User::class,
'role' => 'admin'
],
],
]
И наконец, создаем свой провайдер на основе базового: добавляем поиск по полю role в запрос, который должен возвращать пользователя.
<?php
namespace App\Providers;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Auth::provider('eloquentWithRole', function (Application $app, array $config) {
$userProvider = new EloquentUserProvider($app['hash'], $config['model']);
$userProvider->withQuery(function ($query) use ($config) {
$query->where('role', $config['role']);
});
return $userProvider;
});
}
}
Теперь можно использовать guard admin. Пример для авторизации
Auth::guard('admin')->attempt($credentials)