Laravel Sanctum & Spatie Permission

Полное руководство по аутентификации и управлению правами в Laravel

📚

Введение

Зачем нужны эти пакеты

При разработке современных веб-приложений на Laravel часто возникают две критические задачи:

🔑

Аутентификация API

Безопасная авторизация для SPA, мобильных приложений и внешних сервисов

👥

Управление правами

Гибкая система ролей и прав доступа для различных типов пользователей

🛡️

Безопасность

Защита маршрутов, контроллеров и бизнес-логики от несанкционированного доступа

Laravel Sanctum и Spatie Permission — это два популярных пакета, которые решают эти задачи элегантно и эффективно. В этом руководстве мы подробно разберём оба пакета.

⚔️

Laravel Sanctum

Лёгкая аутентификация для API и SPA

Laravel Sanctum — это простая система аутентификации для одностраничных приложений (SPA), мобильных приложений и API на основе токенов.

Что умеет Sanctum?

🎟️

API Tokens

Генерация персональных токенов доступа для пользователей с возможностью указания прав (abilities)

🍪

SPA Authentication

Аутентификация через cookie-сессии для SPA с защитой от CSRF

📱

Mobile Apps

Идеально подходит для мобильных приложений на Flutter, React Native, и т.д.

Установка Laravel Sanctum

💡 Версия Laravel

Начиная с Laravel 11, Sanctum уже включён в стандартную установку. Для Laravel 10 и ниже нужна ручная установка.

Установка через Composer
📦 Установка пакета Bash
# Для Laravel 10 и ниже
composer require laravel/sanctum

# Для Laravel 11+ (уже установлен, но можно использовать команду)
php artisan install:api
Публикация конфигурации и миграций
⚙️ Публикация файлов Bash
# Для Laravel 11+
php artisan vendor:publish --tag=sanctum-migrations

# Для Laravel 10 и ниже
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Это создаст миграцию для таблицы personal_access_tokens:

📄 Миграция personal_access_tokens PHP


use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
  public function up(): void
  {
      Schema::create('personal_access_tokens', function (Blueprint $table) {
          $table->id();
          $table->morphs('tokenable');
          $table->string('name');
          $table->string('token', 64)->unique();
          $table->text('abilities')->nullable();
          $table->timestamp('last_used_at')->nullable();
          $table->timestamp('expires_at')->nullable();
          $table->timestamps();
      });
  }

  public function down(): void
  {
      Schema::dropIfExists('personal_access_tokens');
  }
};
Запуск миграций
🚀 Выполнение миграции Bash
php artisan migrate
Добавление трейта в модель User
👤 Модель User PHP


namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
  use HasApiTokens;

  // ... остальной код модели
}
Настройка middleware (для SPA)
⚙️ bootstrap/app.php (Laravel 11+) PHP


use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
  ->withRouting(
      web: __DIR__.'/../routes/web.php',
      api: __DIR__.'/../routes/api.php',
      commands: __DIR__.'/../routes/console.php',
  )
  ->withMiddleware(function (Middleware $middleware) {
      $middleware->api(prepend: [
          \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
      ]);
  })
  ->create();
⚠️ Laravel 10 и ниже

В app/Http/Kernel.php добавьте middleware в группу api:

'api' => [
  \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
  \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
  \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Настройка .env
🔧 .env ENV
# Для SPA аутентификации указываем домены frontend
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000

# Домен API
SESSION_DOMAIN=localhost

Использование Sanctum для API токенов

1. Создание токена при входе

🔐 AuthController - метод login PHP


namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

class AuthController extends Controller
{
  /**
   * Вход пользователя
   */
  public function login(Request $request)
  {
      $request->validate([
          'email' => 'required|email',
          'password' => 'required',
      ]);

      $user = User::where('email', $request->email)->first();

      if (!$user || !Hash::check($request->password, $user->password)) {
          return response()->json([
              'message' => 'Неверные учётные данные'
          ], 401);
      }

      // Создание токена с abilities (правами)
      $token = $user->createToken(
          name: 'api-token',
          abilities: ['*'] // все права
      )->plainTextToken;

      return response()->json([
          'access_token' => $token,
          'token_type' => 'Bearer',
          'user' => $user,
      ]);
  }

  /**
   * Выход пользователя
   */
  public function logout(Request $request)
  {
      // Удалить текущий токен
      $request->user()->currentAccessToken()->delete();

      return response()->json([
          'message' => 'Вы успешно вышли'
      ]);
  }

  /**
   * Получить текущего пользователя
   */
  public function user(Request $request)
  {
      return response()->json($request->user());
  }
}

2. Защита маршрутов

🛣️ routes/api.php PHP


use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\ProjectController;

// Публичные маршруты
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);

// Защищённые маршруты (требуют токен)
Route::middleware('auth:sanctum')->group(function () {
  Route::post('/logout', [AuthController::class, 'logout']);
  Route::get('/user', [AuthController::class, 'user']);

  // CRUD для проектов
  Route::apiResource('projects', ProjectController::class);
});

// Маршруты с проверкой abilities (прав токена)
Route::middleware(['auth:sanctum', 'abilities:project:create'])->group(function () {
  Route::post('/projects', [ProjectController::class, 'store']);
});

3. Использование токена на frontend

📱 JavaScript (Axios) JavaScript
// Вход
const response = await axios.post('/api/login', {
email: 'user@example.com',
password: 'password'
});

const token = response.data.access_token;

// Сохранить токен
localStorage.setItem('token', token);

// Использовать токен в запросах
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;

// Запрос к защищённому маршруту
const projects = await axios.get('/api/projects');

// Выход
await axios.post('/api/logout');

4. Abilities (права токена)

🎯 Создание токенов с разными правами PHP


// Токен с полным доступом
$token = $user->createToken('admin-token', ['*'])->plainTextToken;

// Токен только для чтения проектов
$token = $user->createToken('readonly-token', [
    'projects:read',
    'tasks:read'
])->plainTextToken;

// Токен с ограниченными правами
$token = $user->createToken('manager-token', [
    'projects:create',
    'projects:update',
    'tasks:*'
])->plainTextToken;

// Проверка прав в контроллере
if ($request->user()->tokenCan('projects:create')) {
    // Разрешено создавать проекты
}
✨ Совет

Используйте middleware abilities для автоматической проверки прав токена:

Route::middleware(['auth:sanctum', 'abilities:projects:create'])
    ->post('/projects', [ProjectController::class, 'store']);
👑

Spatie Laravel Permission

Мощная система управления ролями и правами

Spatie Laravel Permission — это пакет для управления ролями и правами пользователей. Он позволяет назначать роли и права как пользователям, так и моделям.

Возможности пакета

🎭

Роли

Создание и управление ролями (admin, manager, employee и т.д.)

🔑

Права (Permissions)

Детальные права доступа (users.create, projects.edit и т.д.)

🌐

Multi-tenancy

Поддержка нескольких команд/организаций в одном приложении

🔄

Наследование

Роль может иметь множество прав, пользователь — множество ролей

🚪

Middleware & Gates

Защита маршрутов через middleware и проверка прав через Gates

📦

Кэширование

Автоматическое кэширование прав для высокой производительности

Установка Spatie Permission

Установка через Composer
📦 Установка пакета Bash
composer require spatie/laravel-permission
Публикация конфигурации и миграций
⚙️ Публикация Bash
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

Это создаст несколько файлов:

  • config/permission.php — конфигурация пакета
  • Миграции для таблиц roles, permissions, model_has_roles, model_has_permissions, role_has_permissions
Запуск миграций
🚀 Выполнение миграций Bash
php artisan migrate
💡 Созданные таблицы
  • roles — роли
  • permissions — права
  • model_has_roles — связь модель-роли
  • model_has_permissions — связь модель-права
  • role_has_permissions — связь роли-права
Добавление трейта в модель User
👤 Модель User PHP


namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasRoles;

    // ... остальной код модели
}
Очистка кэша (опционально)
🧹 Очистка кэша прав Bash
php artisan permission:cache-reset

Структура миграций Spatie

📄 Таблица roles PHP
Schema::create('roles', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->string('guard_name');
    $table->timestamps();

    $table->unique(['name', 'guard_name']);
});
📄 Таблица permissions PHP
Schema::create('permissions', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->string('guard_name');
    $table->timestamps();

    $table->unique(['name', 'guard_name']);
});

Использование Spatie Permission

1. Создание ролей и прав через Seeder

🌱 RolePermissionSeeder PHP


namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

class RolePermissionSeeder extends Seeder
{
    public function run(): void
    {
        // Сброс кэша
        app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();

        // Создание прав
        $permissions = [
            // Пользователи
            'users.view',
            'users.create',
            'users.edit',
            'users.delete',

            // Проекты
            'projects.view',
            'projects.create',
            'projects.edit',
            'projects.delete',

            // Задачи
            'tasks.view',
            'tasks.create',
            'tasks.edit',
            'tasks.delete',
        ];

        foreach ($permissions as $permission) {
            Permission::create(['name' => $permission]);
        }

        // Создание ролей и назначение прав

        // Администратор - все права
        $admin = Role::create(['name' => 'admin']);
        $admin->givePermissionTo(Permission::all());

        // Менеджер - управление проектами и задачами
        $manager = Role::create(['name' => 'manager']);
        $manager->givePermissionTo([
            'projects.view',
            'projects.create',
            'projects.edit',
            'tasks.view',
            'tasks.create',
            'tasks.edit',
        ]);

        // Сотрудник - работа с задачами
        $employee = Role::create(['name' => 'employee']);
        $employee->givePermissionTo([
            'projects.view',
            'tasks.view',
            'tasks.create',
            'tasks.edit',
        ]);

        // Клиент - только просмотр
        $client = Role::create(['name' => 'client']);
        $client->givePermissionTo([
            'projects.view',
            'tasks.view',
        ]);
    }
}

2. Назначение ролей пользователям

👥 Работа с ролями PHP


use App\Models\User;

$user = User::find(1);

// Назначить роль
$user->assignRole('admin');
$user->assignRole(['manager', 'employee']); // несколько ролей

// Синхронизировать роли (заменить все текущие)
$user->syncRoles(['manager']);

// Удалить роль
$user->removeRole('employee');

// Проверить роль
if ($user->hasRole('admin')) {
    // Пользователь — администратор
}

// Проверить несколько ролей (хотя бы одна)
if ($user->hasAnyRole(['admin', 'manager'])) {
    // Пользователь — админ или менеджер
}

// Проверить все роли
if ($user->hasAllRoles(['manager', 'employee'])) {
    // Пользователь имеет ОБЕ роли
}

// Получить роли
$roles = $user->getRoleNames(); // Collection ['admin', 'manager']

3. Работа с правами (permissions)

🔑 Работа с правами PHP


use App\Models\User;

$user = User::find(1);

// Назначить право напрямую пользователю
$user->givePermissionTo('tasks.delete');

// Назначить несколько прав
$user->givePermissionTo(['projects.create', 'projects.edit']);

// Синхронизировать права
$user->syncPermissions(['tasks.view', 'tasks.edit']);

// Удалить право
$user->revokePermissionTo('tasks.delete');

// Проверить право
if ($user->can('projects.create')) {
    // Пользователь может создавать проекты
}

// Проверить через hasPermissionTo
if ($user->hasPermissionTo('tasks.edit')) {
    // Разрешено редактировать задачи
}

// Получить все права пользователя (через роли и напрямую)
$permissions = $user->getAllPermissions();

// Получить права только через роли
$permissions = $user->getPermissionsViaRoles();

4. Защита маршрутов через middleware

🛣️ routes/web.php PHP


use Illuminate\Support\Facades\Route;

// Проверка роли
Route::middleware(['role:admin'])->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'index']);
});

// Проверка нескольких ролей (хотя бы одна)
Route::middleware(['role:admin|manager'])->group(function () {
    Route::resource('/projects', ProjectController::class);
});

// Проверка права
Route::middleware(['permission:projects.create'])->group(function () {
    Route::post('/projects', [ProjectController::class, 'store']);
});

// Проверка нескольких прав (все должны быть)
Route::middleware(['permission:projects.edit,projects.delete'])->group(function () {
    Route::delete('/projects/{project}', [ProjectController::class, 'destroy']);
});

// Комбинация роли И права
Route::middleware(['role:manager', 'permission:projects.create'])->group(function () {
    Route::post('/projects', [ProjectController::class, 'store']);
});

5. Использование в контроллерах

🎮 ProjectController PHP


namespace App\Http\Controllers;

use App\Models\Project;
use Illuminate\Http\Request;

class ProjectController extends Controller
{
    public function __construct()
    {
        // Защита всех методов контроллера
        $this->middleware('permission:projects.view')->only(['index', 'show']);
        $this->middleware('permission:projects.create')->only(['create', 'store']);
        $this->middleware('permission:projects.edit')->only(['edit', 'update']);
        $this->middleware('permission:projects.delete')->only('destroy');
    }

    public function store(Request $request)
    {
        // Дополнительная проверка внутри метода
        if (!auth()->user()->can('projects.create')) {
            abort(403, 'У вас нет прав для создания проектов');
        }

        // Создание проекта
        $project = Project::create($request->validated());

        return response()->json($project, 201);
    }

    public function update(Request $request, Project $project)
    {
        // Используем authorize для проверки
        $this->authorize('update', $project);

        $project->update($request->validated());

        return response()->json($project);
    }
}

6. Использование в Blade-шаблонах

🎨 Blade директивы Blade
{{-- Проверка роли --}}
@role('admin')
    <a href="/admin">Панель администратора</a>
@endrole

{{-- Проверка нескольких ролей --}}
@hasanyrole('admin|manager')
    <a href="/projects/create">Создать проект</a>
@endhasanyrole

{{-- Проверка права --}}
@can('projects.create')
    <button>Создать проект</button>
@endcan

{{-- Иначе --}}
@can('projects.edit')
    <button>Редактировать</button>
@elsecannot('projects.edit')
    <p>У вас нет прав на редактирование</p>
@endcan

{{-- Проверка всех ролей --}}
@hasallroles('manager|employee')
    <p>Вы менеджер И сотрудник</p>
@endhasallroles
🔗

Совместное использование

Sanctum + Spatie Permission = ❤️

Sanctum и Spatie Permission прекрасно работают вместе. Sanctum обеспечивает аутентификацию через токены, а Spatie управляет ролями и правами.

Полный пример API-контроллера

🎯 ProjectController с аутентификацией и авторизацией PHP


namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Project;
use Illuminate\Http\Request;

class ProjectController extends Controller
{
    public function __construct()
    {
        // Все методы требуют аутентификации через Sanctum
        $this->middleware('auth:sanctum');

        // Проверка прав через Spatie
        $this->middleware('permission:projects.view')->only(['index', 'show']);
        $this->middleware('permission:projects.create')->only('store');
        $this->middleware('permission:projects.edit')->only('update');
        $this->middleware('permission:projects.delete')->only('destroy');
    }

    /**
     * Получить все проекты
     */
    public function index(Request $request)
    {
        $user = $request->user();

        // Если админ/менеджер — показываем все проекты
        if ($user->hasAnyRole(['admin', 'manager'])) {
            $projects = Project::with('client', 'manager')->get();
        } else {
            // Обычный пользователь видит только свои проекты
            $projects = Project::where('manager_id', $user->id)->get();
        }

        return response()->json($projects);
    }

    /**
     * Создать проект
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'client_id' => 'required|exists:clients,id',
            'description' => 'nullable|string',
            'budget' => 'nullable|numeric',
            'deadline' => 'nullable|date',
        ]);

        $project = Project::create([
            ...$validated,
            'manager_id' => $request->user()->id,
            'status' => 'planning',
        ]);

        return response()->json($project, 201);
    }

    /**
     * Обновить проект
     */
    public function update(Request $request, Project $project)
    {
        $user = $request->user();

        // Дополнительная проверка: только менеджер проекта или админ может редактировать
        if (!$user->hasRole('admin') && $project->manager_id !== $user->id) {
            return response()->json([
                'message' => 'У вас нет прав для редактирования этого проекта'
            ], 403);
        }

        $validated = $request->validate([
            'name' => 'sometimes|string|max:255',
            'description' => 'nullable|string',
            'budget' => 'nullable|numeric',
            'status' => 'sometimes|in:planning,active,on_hold,completed,cancelled',
        ]);

        $project->update($validated);

        return response()->json($project);
    }

    /**
     * Удалить проект
     */
    public function destroy(Project $project)
    {
        // Только админы могут удалять проекты
        if (!auth()->user()->hasRole('admin')) {
            return response()->json([
                'message' => 'Только администраторы могут удалять проекты'
            ], 403);
        }

        $project->delete();

        return response()->json(['message' => 'Проект удалён'], 200);
    }
}

Маршруты с защитой

🛣️ routes/api.php PHP


use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\ProjectController;
use App\Http\Controllers\Api\UserController;

// Публичные маршруты
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);

// Защищённые маршруты (требуют токен Sanctum)
Route::middleware('auth:sanctum')->group(function () {

    // Профиль пользователя
    Route::get('/user', [AuthController::class, 'user']);
    Route::post('/logout', [AuthController::class, 'logout']);

    // Проекты (все авторизованные могут просматривать)
    Route::get('/projects', [ProjectController::class, 'index'])
        ->middleware('permission:projects.view');

    Route::get('/projects/{project}', [ProjectController::class, 'show'])
        ->middleware('permission:projects.view');

    // Создание проектов (только менеджеры и админы)
    Route::post('/projects', [ProjectController::class, 'store'])
        ->middleware(['role:admin|manager', 'permission:projects.create']);

    // Редактирование проектов
    Route::put('/projects/{project}', [ProjectController::class, 'update'])
        ->middleware('permission:projects.edit');

    // Удаление проектов (только админы)
    Route::delete('/projects/{project}', [ProjectController::class, 'destroy'])
        ->middleware(['role:admin', 'permission:projects.delete']);

    // Управление пользователями (только админы)
    Route::middleware(['role:admin'])->group(function () {
        Route::apiResource('users', UserController::class);
        Route::post('/users/{user}/assign-role', [UserController::class, 'assignRole']);
        Route::post('/users/{user}/give-permission', [UserController::class, 'givePermission']);
    });
});
✨ Лучшая практика

Используйте Sanctum для аутентификации пользователей (проверка токена), а Spatie Permission для авторизации (проверка прав и ролей).

⚖️

Сравнение и выбор

Когда использовать каждый пакет

Критерий Laravel Sanctum Spatie Permission
Основная задача Аутентификация API через токены Управление ролями и правами
Когда использовать SPA, мобильные приложения, API Сложные системы с разными типами пользователей
Сложность Простой в освоении Средняя сложность
Производительность Быстрый Быстрый (с кэшированием)
Гибкость Ограниченная (только токены) Очень гибкая система прав
Поддержка команд Нет Да (multi-tenancy)

Рекомендации по выбору

🎯

Используйте Sanctum, если:

  • Вам нужна простая API-аутентификация
  • Разрабатываете SPA (Vue/React)
  • Создаёте мобильное приложение
  • Нужны персональные токены доступа
👑

Используйте Spatie, если:

  • Нужна сложная система ролей
  • Разные типы пользователей с разными правами
  • Multi-tenancy (несколько организаций)
  • Детальный контроль доступа
💡

Используйте оба вместе, если:

  • Разрабатываете полноценный SaaS-сервис
  • API с детальным контролем прав
  • Мобильное приложение + веб-админка
  • Нужна максимальная гибкость
🚀

Практические примеры

Реальные сценарии использования

Пример 1: Регистрация и вход

🔐 AuthController с назначением роли PHP


namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    /**
     * Регистрация нового пользователя
     */
    public function register(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8|confirmed',
        ]);

        $user = User::create([
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => Hash::make($validated['password']),
        ]);

        // Назначаем роль "employee" по умолчанию
        $user->assignRole('employee');

        // Создаём токен
        $token = $user->createToken('auth-token')->plainTextToken;

        return response()->json([
            'user' => $user->load('roles'),
            'access_token' => $token,
            'token_type' => 'Bearer',
        ], 201);
    }

    /**
     * Вход пользователя
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) {
            return response()->json([
                'message' => 'Неверные учётные данные'
            ], 401);
        }

        // Удаляем старые токены (опционально)
        $user->tokens()->delete();

        // Создаём новый токен с abilities на основе прав пользователя
        $abilities = $user->getAllPermissions()->pluck('name')->toArray();
        $token = $user->createToken('auth-token', $abilities)->plainTextToken;

        return response()->json([
            'user' => $user->load('roles', 'permissions'),
            'access_token' => $token,
            'token_type' => 'Bearer',
            'abilities' => $abilities,
        ]);
    }

    /**
     * Получить профиль текущего пользователя
     */
    public function user(Request $request)
    {
        return response()->json([
            'user' => $request->user()->load('roles', 'permissions'),
            'roles' => $request->user()->getRoleNames(),
            'permissions' => $request->user()->getAllPermissions()->pluck('name'),
        ]);
    }

    /**
     * Выход
     */
    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();

        return response()->json([
            'message' => 'Вы успешно вышли'
        ]);
    }
}

Пример 2: Управление ролями (только для админов)

👑 RoleController PHP


namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

class RoleController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth:sanctum', 'role:admin']);
    }

    /**
     * Получить все роли с правами
     */
    public function index()
    {
        $roles = Role::with('permissions')->get();

        return response()->json($roles);
    }

    /**
     * Создать новую роль
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|unique:roles,name',
            'permissions' => 'array',
            'permissions.*' => 'exists:permissions,name',
        ]);

        $role = Role::create(['name' => $validated['name']]);

        if (!empty($validated['permissions'])) {
            $role->givePermissionTo($validated['permissions']);
        }

        return response()->json($role->load('permissions'), 201);
    }

    /**
     * Назначить роль пользователю
     */
    public function assignToUser(Request $request, User $user)
    {
        $validated = $request->validate([
            'role' => 'required|exists:roles,name',
        ]);

        $user->assignRole($validated['role']);

        return response()->json([
            'message' => 'Роль успешно назначена',
            'user' => $user->load('roles'),
        ]);
    }

    /**
     * Синхронизировать права роли
     */
    public function syncPermissions(Request $request, Role $role)
    {
        $validated = $request->validate([
            'permissions' => 'required|array',
            'permissions.*' => 'exists:permissions,name',
        ]);

        $role->syncPermissions($validated['permissions']);

        // Сбросить кэш прав
        app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();

        return response()->json([
            'message' => 'Права роли обновлены',
            'role' => $role->load('permissions'),
        ]);
    }

    /**
     * Получить все права
     */
    public function permissions()
    {
        $permissions = Permission::all()->groupBy('guard_name');

        return response()->json($permissions);
    }
}

Пример 3: Frontend (Vue.js)

🎨 Vue 3 Composition API JavaScript
// stores/auth.js
import { ref, computed } from 'vue'
import axios from 'axios'

export const useAuthStore = () => {
  const user = ref(null)
  const token = ref(localStorage.getItem('token') || null)
  const roles = ref([])
  const permissions = ref([])

  const isAuthenticated = computed(() => !!token.value)

  const hasRole = (role) => {
    return roles.value.includes(role)
  }

  const hasPermission = (permission) => {
    return permissions.value.includes(permission)
  }

  const hasAnyRole = (roleArray) => {
    return roleArray.some(role => roles.value.includes(role))
  }

  const login = async (credentials) => {
    try {
      const response = await axios.post('/api/login', credentials)

      token.value = response.data.access_token
      user.value = response.data.user
      roles.value = response.data.user.roles.map(r => r.name)
      permissions.value = response.data.abilities

      localStorage.setItem('token', token.value)
      axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`

      return response.data
    } catch (error) {
      throw error
    }
  }

  const logout = async () => {
    try {
      await axios.post('/api/logout')
    } finally {
      user.value = null
      token.value = null
      roles.value = []
      permissions.value = []
      localStorage.removeItem('token')
      delete axios.defaults.headers.common['Authorization']
    }
  }

  const fetchUser = async () => {
    try {
      const response = await axios.get('/api/user')
      user.value = response.data.user
      roles.value = response.data.roles
      permissions.value = response.data.permissions
    } catch (error) {
      await logout()
    }
  }

  // Инициализация при загрузке
  if (token.value) {
    axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
    fetchUser()
  }

  return {
    user,
    token,
    roles,
    permissions,
    isAuthenticated,
    hasRole,
    hasPermission,
    hasAnyRole,
    login,
    logout,
    fetchUser,
  }
}

// Использование в компоненте
// ProjectList.vue
import { useAuthStore } from '@/stores/auth'
import { ref, onMounted } from 'vue'
import axios from 'axios'

const auth = useAuthStore()
const projects = ref([])

const fetchProjects = async () => {
  const response = await axios.get('/api/projects')
  projects.value = response.data
}

const createProject = async (projectData) => {
  if (!auth.hasPermission('projects.create')) {
    alert('У вас нет прав для создания проектов')
    return
  }

  const response = await axios.post('/api/projects', projectData)
  projects.value.push(response.data)
}

onMounted(() => {
  fetchProjects()
})

Пример 4: Middleware для проверки прав владельца

🛡️ CheckProjectOwner Middleware PHP


namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\Project;

class CheckProjectOwner
{
    /**
     * Проверяет, является ли пользователь владельцем/менеджером проекта или админом
     */
    public function handle(Request $request, Closure $next)
    {
        $user = $request->user();
        $projectId = $request->route('project');

        // Админы имеют доступ ко всему
        if ($user->hasRole('admin')) {
            return $next($request);
        }

        $project = Project::findOrFail($projectId);

        // Проверяем, является ли пользователь менеджером проекта
        if ($project->manager_id === $user->id) {
            return $next($request);
        }

        // Проверяем, является ли пользователь участником проекта
        if ($project->members()->where('user_id', $user->id)->exists()) {
            return $next($request);
        }

        abort(403, 'У вас нет доступа к этому проекту');
    }
}

// Регистрация middleware в bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'project.owner' => \App\Http\Middleware\CheckProjectOwner::class,
    ]);
})

// Использование в маршрутах
Route::middleware(['auth:sanctum', 'project.owner'])->group(function () {
    Route::get('/projects/{project}', [ProjectController::class, 'show']);
    Route::put('/projects/{project}', [ProjectController::class, 'update']);
});
💡 Совет

Комбинируйте проверку ролей/прав Spatie с кастомными middleware для более точного контроля доступа на уровне владения ресурсами.

Полезные команды

Артизан-команды для работы с пакетами

Laravel Sanctum

📟 Команды Sanctum Bash
# Установка API-маршрутов (Laravel 11+)
php artisan install:api

# Публикация конфигурации
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

# Публикация только миграций
php artisan vendor:publish --tag=sanctum-migrations

# Запуск миграций
php artisan migrate

# Проверка токенов в Tinker
php artisan tinker
>>> $user = User::first()
>>> $user->tokens
>>> $user->createToken('test-token')->plainTextToken

Spatie Permission

📟 Команды Spatie Permission Bash
# Публикация конфигурации и миграций
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

# Запуск миграций
php artisan migrate

# Очистка кэша прав
php artisan permission:cache-reset

# Создание права через Tinker
php artisan tinker
>>> Permission::create(['name' => 'users.create'])
>>> Role::create(['name' => 'moderator'])

# Назначение роли
>>> $user = User::find(1)
>>> $user->assignRole('admin')
>>> $user->givePermissionTo('users.create')

# Проверка
>>> $user->hasRole('admin')
>>> $user->can('users.create')

Создание сидера для быстрого старта

🌱 Команда создания сидера Bash
# Создать сидер для ролей и прав
php artisan make:seeder RolePermissionSeeder

# Запустить все сидеры
php artisan db:seed

# Запустить конкретный сидер
php artisan db:seed --class=RolePermissionSeeder

# Пересоздать базу и запустить сидеры
php artisan migrate:fresh --seed
🔧

Решение проблем

Частые ошибки и их решения

⚠️ 401 Unauthenticated

Проблема: API возвращает 401 даже с токеном

Решение:

  • Проверьте, что токен передаётся в заголовке: Authorization: Bearer TOKEN
  • Убедитесь, что middleware auth:sanctum подключён
  • Проверьте, что трейт HasApiTokens добавлен в модель User
  • Очистите кэш: php artisan config:clear
⚠️ 403 Forbidden при использовании Spatie

Проблема: Пользователь не может получить доступ, хотя права назначены

Решение:

  • Очистите кэш прав: php artisan permission:cache-reset
  • Проверьте guard_name (должен быть 'web' или 'api')
  • Убедитесь, что трейт HasRoles добавлен в модель User
  • Проверьте написание имени права (регистр важен!)
🚨 CORS ошибки для SPA

Проблема: SPA не может обращаться к API

Решение:

  • Настройте config/cors.php
  • Добавьте домен frontend в SANCTUM_STATEFUL_DOMAINS
  • Для cookie-based аутентификации используйте /sanctum/csrf-cookie
💡 Производительность

Совет: Spatie кэширует права автоматически, но при изменениях нужно сбрасывать кэш:

// После изменения прав
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
🎓

Заключение

Что вы узнали

В этом руководстве вы познакомились с двумя мощными пакетами Laravel:

⚔️

Laravel Sanctum

  • Установка и настройка
  • API-токены и их использование
  • SPA аутентификация
  • Abilities (права токенов)
👑

Spatie Permission

  • Система ролей и прав
  • Миграции и сидеры
  • Middleware для защиты маршрутов
  • Проверка прав в коде и Blade
🚀

Практика

  • Реальные примеры кода
  • Совместное использование пакетов
  • Frontend интеграция
  • Решение проблем
🎯 Следующие шаги
  • Создайте свой первый проект с этими пакетами
  • Изучите документацию: Sanctum и Spatie Permission
  • Попробуйте реализовать multi-tenancy
  • Добавьте тесты для проверки прав