Личный кабинет на Laravel с ролями - это связка из аутентификации, модели прав доступа и интерфейса, который меняется в зависимости от того, кто зашёл: администратор, менеджер или обычный клиент. За последние пару лет я собирал такие кабинеты для SaaS-сервисов на подписке, для внутренних CRM с пятью уровнями доступа и для маркетплейсов, где продавец и покупатель видят разные разделы одного и того же приложения. Ниже - рабочий подход: с чего стартовать, как построить ролевую модель без костылей и где обычно вылезают баги.
Архитектура личного кабинета с разграничением доступа на Laravel
Прежде чем писать код, я фиксирую три вещи: список ролей, список разделов кабинета и матрицу - кто что видит и что может редактировать. Без этой таблицы разработчик обычно начинает с одной роли admin, потом добавляет user, а через месяц выясняется, что нужен ещё и moderator с урезанными правами - и всю систему приходится переписывать.
Типовая структура для кабинета с ролями выглядит так:
- таблица
users- базовые данные аккаунта; - таблица
roles- справочник ролей (admin, manager, client и так далее); - таблица
permissions- конкретные права (view-orders, edit-users, export-reports); - связующие таблицы
model_has_rolesиrole_has_permissions- кто какую роль носит и что она даёт.
Эта схема - стандарт де-факто в экосистеме Laravel, её реализует пакет Spatie Laravel-Permission, и переизобретать её вручную смысла нет: 90% задач по разграничению доступа она закрывает из коробки.
Установка Laravel Breeze и базовая аутентификация для кабинета
Для личного кабинета я почти всегда беру Breeze - он лёгкий, даёт готовые формы логина, регистрации, сброса пароля и не тащит лишний JS-стек, если фронт собирается отдельно на Vue или React. Jetstream имею в виду только когда нужны команды (teams) и двухфакторная аутентификация из коробки - для 80% кабинетов это избыточно.
Установка:
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install && npm run build
php artisan migrate
После миграции в таблице users есть базовые поля, но роли туда добавлять полем role = 'admin' - плохая идея: как только появится вторая роль на пользователя (например, менеджер, который одновременно и клиент в своём личном разделе), схема сломается. Поэтому сразу подключаю пакет для ролей, а не строю на голом enum.
Ролевая модель через Spatie Laravel-Permission
Ставлю пакет и публикую миграции:
composer require spatie/laravel-permission
php artisan vendor:publish --provider="SpatiePermissionPermissionServiceProvider"
php artisan migrate
В модель User добавляю трейт:
use SpatiePermissionTraitsHasRoles;
class User extends Authenticatable
{
use HasRoles;
}
Дальше в сидере создаю роли и права:
use SpatiePermissionModelsRole;
use SpatiePermissionModelsPermission;
Permission::create(['name' => 'view-orders']);
Permission::create(['name' => 'edit-users']);
$admin = Role::create(['name' => 'admin']);
$admin->givePermissionTo(['view-orders', 'edit-users']);
$manager = Role::create(['name' => 'manager']);
$manager->givePermissionTo('view-orders');
$user->assignRole('admin');
Плюс такого подхода - гибкость: одному пользователю можно назначить несколько ролей, а права проверять и по роли, и по конкретному permission, не привязываясь жёстко к названию роли в коде.
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
Middleware и защита маршрутов по ролям
После установки Spatie регистрирую middleware-алиасы в bootstrap/app.php (Laravel 11) или app/Http/Kernel.php для более старых версий:
$middleware->alias([
'role' => SpatiePermissionMiddlewareRoleMiddleware::class,
'permission' => SpatiePermissionMiddlewarePermissionMiddleware::class,
]);
Закрываю маршруты по ролям прямо в группах:
Route::middleware(['auth', 'role:admin'])->prefix('admin')->group(function () {
Route::get('/users', [AdminController::class, 'users']);
});
Route::middleware(['auth', 'permission:view-orders'])->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
});
Важный момент, который часто упускают: middleware закрывает маршрут, но не закрывает данные внутри контроллера. Если менеджер должен видеть только заказы своего филиала, а не все заказы в базе, фильтрацию по where('branch_id', auth()->user()->branch_id) нужно прописывать отдельно - Spatie про это ничего не знает, это уже зона Eloquent-скоупов и политик (Policy).
В Blade-шаблонах права проверяю directives:
@role('admin')
<a href="/admin/users">Управление пользователями</a>
@endrole
Интерфейс личного кабинета с учётом уровней доступа
Одна ошибка, которую я видел в чужих проектах чаще всего, - разработчик делает один общий dashboard.blade.php с десятком @if(auth()->user()->role == 'admin') внутри. Через полгода такой шаблон превращается в нечитаемую простыню условий, где страшно что-то менять.
Я разбиваю кабинет по layout’ам на уровне ролей:
resources/views/dashboard/admin/*- полный набор виджетов и управление пользователями;resources/views/dashboard/manager/*- заказы, отчёты, без доступа к настройкам биллинга;resources/views/dashboard/client/*- только личные заказы, профиль, оплата.
В контроллере после логина редиректю на нужный layout в зависимости от роли пользователя, а не городю единый шаблон с ветвлениями. Это не только чище с точки зрения кода, но и снижает риск, что менеджеру случайно покажут кнопку, которая физически ведёт на закрытый для него роут.
Если кабинет предполагает оплату подписки или разовые платежи - раздел биллинга обычно завязываю на Т‑Кассу (бывший Тинькофф Кассу) или ЮKassa, в зависимости от того, что уже подключено у клиента к расчётному счёту; логика ролей на выбор эквайринга не влияет, но статус оплаты часто становится дополнительным условием доступа (например, раздел «Аналитика» открыт только клиентам с активной подпиской).
Типичные ошибки при разработке личного кабинета с ролями
За несколько проектов накопился список повторяющихся проблем:
- Роль хранится строкой в users, а не через связующую таблицу - ломается при первой попытке дать пользователю две роли или временно расширить права.
- Проверка прав только на фронте - скрыли кнопку в Blade, а сам роут остался открытым; любой, кто узнает URL, получает доступ.
- Нет кеширования permissions - Spatie по умолчанию кеширует права на 24 часа, но если права меняются часто (например, в панели администратора можно на лету включать/выключать доступ), кеш нужно сбрасывать вручную через
app()[SpatiePermissionPermissionRegistrar::class]->forgetCachedPermissions(). - Сидеры ролей не версионируются - на проде роли раздают руками через тинкер, а через полгода никто не помнит, у кого какие права и почему.
Чтобы не собирать миграции и сидеры ролей заново на каждом проекте, я оформил готовые заготовки миграций и сидеров для ролевой модели на Spatie - беру их как стартовую точку и донастраиваю под конкретные права заказчика.
Сроки и стоимость разработки личного кабинета с ролями
Цифры сильно зависят от количества ролей и объёма функциональности внутри кабинета (профиль, заказы, биллинг, экспорт отчётов, интеграция с CRM или СДЭК для отслеживания доставки). Ориентировочное сравнение:
| Вариант | Срок | Стоимость |
|---|---|---|
| Студия под ключ (дизайн + бэкенд + фронт) | 4-8 недель | на рынке студии просят от 200 000 до 500 000 ₽ |
| Фрилансер на бирже | 2-5 недель | на рынке разброс от 50 000 до 150 000 ₽, качество кода нестабильно |
| Бэкенд на Laravel с ролями у меня (2-4 роли, Breeze + Spatie, кабинет без сложного биллинга) | 2-3 недели | от 100 000 ₽ |
| То же с интеграцией эквайринга (Т‑Касса/ЮKassa) и СДЭК-трекингом заказов | 3-5 недель | от 150 000 ₽ |
Если параллельно нужна автоматизация вокруг кабинета - например, уведомления в Telegram при смене статуса заказа или выгрузка данных в n8n для отчётности, - это считается отдельно и обычно добавляет от 25 000 ₽ на сценарий в n8n или от 30 000 ₽ на Telegram-бота.
API и серверная часть под SPA
API / Бэкенд
от 100 000 ₽
Подробнее →Частые вопросы
Чем ролевая модель Spatie отличается от простого поля role в таблице users?
Поле role строкой подходит только если у пользователя ровно одна роль на всё время жизни аккаунта. Spatie хранит роли и права в отдельных таблицах со связью многие-ко-многим - пользователь может одновременно быть менеджером и модератором, права можно назначать не только через роль, но и точечно конкретному человеку, а сама модель легко расширяется без изменения структуры таблицы users.
Можно ли сделать личный кабинет с ролями без Laravel Breeze или Jetstream?
Да, аутентификацию и роли можно собрать полностью вручную на Laravel Sanctum или встроенных Guard’ах - иногда так и делаю, если кабинет отдаёт API для SPA на Vue или React и Blade-формы вообще не нужны. Breeze просто экономит время на форму логина, регистрацию и сброс пароля, которые иначе пришлось бы писать с нуля.
Как ограничить доступ не только к разделам, но и к данным - например, чтобы менеджер видел только своих клиентов?
Это закрывается не middleware, а Policy и Eloquent-скоупами. Middleware решает «может ли роль вообще открыть этот роут», а фильтрация данных внутри контроллера или через global scope в модели решает «какие именно записи из базы увидит конкретный пользователь». Обе части нужны одновременно, одна без другой не даёт полноценного разграничения доступа.
Сколько ролей закладывать в кабинет на старте проекта?
Обычно хватает 3-4: администратор, менеджер (или сотрудник), клиент, и иногда гость с ограниченным просмотром. Закладывать про запас десяток ролей на будущее не советую - таблица прав быстро становится нечитаемой, а Spatie позволяет добавить новую роль и раздать ей права за 10 минут в любой момент, когда она реально понадобится.