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 11, Sanctum уже включён в стандартную установку. Для Laravel 10 и ниже нужна ручная установка.
# Для Laravel 10 и ниже
composer require laravel/sanctum
# Для Laravel 11+ (уже установлен, но можно использовать команду)
php artisan install:api
# Для Laravel 11+
php artisan vendor:publish --tag=sanctum-migrations
# Для Laravel 10 и ниже
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Это создаст миграцию для таблицы personal_access_tokens:
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');
}
};
php artisan migrate
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
// ... остальной код модели
}
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();
В app/Http/Kernel.php добавьте middleware в группу api:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
# Для SPA аутентификации указываем домены frontend
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
# Домен API
SESSION_DOMAIN=localhost
Использование Sanctum для API токенов
1. Создание токена при входе
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. Защита маршрутов
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
// Вход
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 (права токена)
// Токен с полным доступом
$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 require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
Это создаст несколько файлов:
config/permission.php— конфигурация пакета- Миграции для таблиц
roles,permissions,model_has_roles,model_has_permissions,role_has_permissions
php artisan migrate
roles— ролиpermissions— праваmodel_has_roles— связь модель-ролиmodel_has_permissions— связь модель-праваrole_has_permissions— связь роли-права
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;
// ... остальной код модели
}
php artisan permission:cache-reset
Структура миграций Spatie
Schema::create('roles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('guard_name');
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
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
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. Назначение ролей пользователям
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)
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
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. Использование в контроллерах
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-шаблонах
{{-- Проверка роли --}}
@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-контроллера
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);
}
}
Маршруты с защитой
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: Регистрация и вход
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: Управление ролями (только для админов)
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)
// 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 для проверки прав владельца
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
# Установка 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
# Публикация конфигурации и миграций
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')
Создание сидера для быстрого старта
# Создать сидер для ролей и прав
php artisan make:seeder RolePermissionSeeder
# Запустить все сидеры
php artisan db:seed
# Запустить конкретный сидер
php artisan db:seed --class=RolePermissionSeeder
# Пересоздать базу и запустить сидеры
php artisan migrate:fresh --seed
Решение проблем
Частые ошибки и их решения
Проблема: API возвращает 401 даже с токеном
Решение:
- Проверьте, что токен передаётся в заголовке:
Authorization: Bearer TOKEN - Убедитесь, что middleware
auth:sanctumподключён - Проверьте, что трейт
HasApiTokensдобавлен в модель User - Очистите кэш:
php artisan config:clear
Проблема: Пользователь не может получить доступ, хотя права назначены
Решение:
- Очистите кэш прав:
php artisan permission:cache-reset - Проверьте guard_name (должен быть 'web' или 'api')
- Убедитесь, что трейт
HasRolesдобавлен в модель User - Проверьте написание имени права (регистр важен!)
Проблема: 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
- Добавьте тесты для проверки прав