Laravel Sanctum для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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, 'Д                    

Рекомендаций