Laravel Sanctum для PHP: полное руководство по API токенам и SPA аутентификации
1. Что это и зачем нужна
Laravel Sanctum — это легковесная библиотека аутентификации для PHP-фреймворка Laravel, предназначенная для управления API-токенами и одностраничными приложениями (SPA). Разработанная Taylor Otwell и командой Laravel, Sanctum предоставляет простой, но мощный способ защиты API-маршрутов, позволяя разработчикам быстро внедрить аутентификацию без сложной настройки, характерной для более тяжеловесных решений, таких как Passport (OAuth2).
Основная задача Sanctum — решить две ключевые проблемы современной веб-разработки: аутентификация для SPA (Single Page Applications), работающих на том же домене, что и бэкенд, и выдача API-токенов для мобильных приложений или сторонних сервисов. В отличие от Passport, Sanctum не использует полноценный протокол OAuth2, что делает его идеальным выбором для проектов, где не требуется сложная система делегирования доступа. Он использует сессии Laravel для SPA-аутентификации и простые токены на основе HMAC для API.
Библиотека входит в стандартную поставку Laravel 8 и выше, что делает её доступной "из коробки". Sanctum особенно полезен для стартапов, небольших команд и проектов, где важна скорость разработки и минимальное количество зависимостей. Он автоматически управляет сроками действия токенов, поддерживает "способности" (abilities) для тонкой настройки прав доступа и легко интегрируется с Laravel Fortify, Breeze или Jetstream.
2. Установка
Laravel Sanctum устанавливается через Composer — стандартный менеджер пакетов для PHP. Убедитесь, что у вас установлен PHP 8.0 или выше и Laravel 8.x/9.x/10.x/11.x.
Точная команда для установки:
composer require laravel/sanctum
После установки необходимо опубликовать конфигурационный файл и миграции базы данных:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Затем выполните миграции, чтобы создать таблицу personal_access_tokens:
php artisan migrate
Для SPA-аутентификации дополнительно нужно добавить middleware EnsureFrontendRequestsAreStateful в ядро HTTP (файл app/Http/Kernel.php):
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Также в файле .env укажите домен вашего SPA:
SANCTUM_STATEFUL_DOMAINS=localhost:8080
3. Быстрый старт — минимальный рабочий пример
Рассмотрим минимальный пример выдачи API-токена и защиты маршрута.
Шаг 1: Модель User
В модели App\Models\User используйте трейт HasApiTokens:
Шаг 2: Маршрут для выдачи токена
Создайте маршрут в routes/api.php:
validate([
'email' => 'required|email',
'password' => 'required',
]);
if (auth()->attempt($credentials)) {
$user = auth()->user();
$token = $user->createToken('api-token')->plainTextToken;
return response()->json([
'token' => $token,
'user' => $user
]);
}
return response()->json(['message' => 'Invalid credentials'], 401);
});
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Шаг 3: Тестирование
Отправьте POST-запрос на /api/login с email и паролем. В ответ получите токен. Затем используйте этот токен в заголовке Authorization: Bearer {token} для доступа к /api/user.
4. Основные методы/функции/классы
4.1. createToken(string $name, array $abilities = ['*'])
Сигнатура: public function createToken(string $name, array $abilities = ['*']): Laravel\Sanctum\NewAccessToken
Что делает: Создает новый API-токен для пользователя. Возвращает объект NewAccessToken, содержащий plain-text токен и его хеш.
$user = User::find(1);
$token = $user->createToken('mobile-app', ['read', 'write']);
echo $token->plainTextToken; // Вывод: 1|abc123...
4.2. tokens()
Сигнатура: public function tokens(): MorphMany
Что делает: Возвращает коллекцию всех токенов, принадлежащих пользователю. Позволяет управлять токенами (просмотр, удаление).
$user = User::find(1);
foreach ($user->tokens as $token) {
echo $token->name . ' - ' . $token->created_at;
}
4.3. currentAccessToken()
Сигнатура: public function currentAccessToken(): ?Laravel\Sanctum\PersonalAccessToken
Что делает: Возвращает модель текущего токена, используемого для аутентификации запроса. Полезно для проверки способностей токена.
Route::middleware('auth:sanctum')->get('/check-ability', function (Request $request) {
$token = $request->user()->currentAccessToken();
if ($token && $token->can('admin')) {
return 'У вас есть права администратора';
}
return 'Нет прав';
});
4.4. tokenCan(string $ability)
Сигнатура: public function tokenCan(string $ability): bool
Что делает: Проверяет, имеет ли текущий токен указанную способность. Используется для авторизации на уровне токена.
Route::middleware('auth:sanctum')->get('/admin', function (Request $request) {
if ($request->user()->tokenCan('admin')) {
return 'Доступ к админке разрешен';
}
abort(403, 'Недостаточно прав');
});
4.5. deleteTokens()
Сигнатура: public function deleteTokens(): void
Что делает: Удаляет все токены текущего пользователя. Полезно для функции "выйти из всех устройств".
Route::middleware('auth:sanctum')->post('/logout-all', function (Request $request) {
$request->user()->deleteTokens();
return response()->json(['message' => 'Вы вышли из всех устройств']);
});
4.6. revoke() (на модели PersonalAccessToken)
Сигнатура: public function revoke(): bool
Что делает: Отзывает (помечает как удаленный) конкретный токен. Токен перестает работать, но запись остается в БД.
$token = PersonalAccessToken::findToken($tokenId);
if ($token) {
$token->revoke();
return 'Токен отозван';
}
4.7. findToken(string $token)
Сигнатура: public static function findToken(string $token): ?Laravel\Sanctum\PersonalAccessToken
Что делает: Находит модель токена по его plain-text значению. Используется для ручной проверки токена.
use Laravel\Sanctum\PersonalAccessToken;
$token = PersonalAccessToken::findToken($plainTextToken);
if ($token && $token->can('read')) {
$user = $token->tokenable;
// Работа с пользователем
}
4.8. abilities()
Сигнатура: public function abilities(): array
Что делает: Возвращает массив способностей (abilities), связанных с токеном. Позволяет просматривать права токена.
$user = User::find(1);
$token = $user->createToken('test', ['read', 'write']);
echo implode(', ', $token->accessToken->abilities); // Вывод: read, write
4.9. expiresAt() (конфигурация)
Сигнатура: public function expiresAt(): ?Carbon
Что делает: Возвращает дату истечения срока действия токена, если она установлена. Sanctum может быть настроен на автоматическое истечение токенов.
// В config/sanctum.php
'expiration' => 60, // Токены истекают через 60 минут
// В коде
$token = $user->createToken('session');
echo $token->accessToken->expires_at; // 2024-01-01 12:00:00
5. Реальный практический пример (100-200 строк кода)
Создадим полноценный REST API для управления задачами (Task Manager) с аутентификацией через Sanctum. Пример включает регистрацию, логин, CRUD для задач и проверку способностей.
Файл: routes/api.php
group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
// Маршруты для задач
Route::apiResource('tasks', TaskController::class);
// Маршрут для администраторов
Route::middleware('ability:admin')->get('/admin/stats', function () {
return [
'users_count' => \App\Models\User::count(),
'tasks_count' => \App\Models\Task::count(),
];
});
});
Файл: app/Http/Controllers/AuthController.php
validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken('auth-token', ['read', 'write'])->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
], 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)) {
throw ValidationException::withMessages([
'email' => ['Неверные учетные данные.'],
]);
}
// Удаляем старые токены, если нужно
// $user->deleteTokens();
$token = $user->createToken('auth-token', ['read', 'write'])->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Вы вышли из системы']);
}
public function logoutAll(Request $request)
{
$request->user()->deleteTokens();
return response()->json(['message' => 'Вы вышли из всех устройств']);
}
}
Файл: app/Http/Controllers/TaskController.php
user()->tasks()->paginate(10);
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'status' => 'in:pending,completed',
]);
$task = $request->user()->tasks()->create($validated);
return response()->json($task, 201);
}
public function show(Request $request, Task $task)
{
// Проверяем, принадлежит ли задача пользователю
if ($request->user()->id !== $task->user_id) {
abort(403, 'Д