Разработка · 6 мин чтения

Личный кабинет на Laravel с ролями пользователей: пошаговая реализация

Личный кабинет на 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 минут в любой момент, когда она реально понадобится.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно