Laravel API с Sanctum
Laravel API с Sanctum
Первая программа на Laravel строит классический сайт — формы, Blade, сессия в cookie. Современные проекты часто отдают JSON отдельному клиенту — SPA на Vue/React, мобильное приложение, другой сервис.
API (Application Programming Interface) здесь — набор URL, которые принимают и возвращают JSON вместо HTML.
Laravel Sanctum выдаёт пользователю API-токен — длинную строку, которую клиент передаёт в заголовке Authorization: Bearer … при каждом запросе. Это проще полноценного OAuth2 для "своего" фронта и мобильных приложений.
Обзор MVC: Laravel 143.md. Альтернатива в Symfony: Symfony.
Web и API — в чём разница
routes/web.php | routes/api.php | |
|---|---|---|
| Ответ | HTML (Blade) | JSON |
| Сессия | Cookie, CSRF-токен в формах | Обычно без cookie-сессии |
| Префикс URL | / | /api/… (Laravel добавляет автоматически) |
| Middleware | web (CSRF, сессия) | api (throttle, без CSRF для токена) |
Ошибка 419 Page Expired при POST в API чаще всего значит, что запрос попал в web.php и Laravel ждёт CSRF-токен. API-маршруты держите в api.php.
Термины
| Термин | Значение |
|---|---|
| JSON | Текстовый формат { "key": "value" } для обмена данными |
| Bearer token | Токен в заголовке Authorization: Bearer abc123… |
| Resource | Класс, который задаёт, какие поля модели уходят в JSON |
| Sanctum | Пакет Laravel для токенов и SPA-аутентификации |
| 401 Unauthorized | "Не знаю, кто ты" — нет или неверный токен |
| 403 Forbidden | "Знаю, но нельзя" — Policy запретила действие |
Что получится в конце
| Шаг | Результат |
|---|---|
GET /api/notes без токена | 401 |
POST /api/login с email и password | JSON { "token": "…" } |
Запросы с Authorization: Bearer … | CRUD заметок |
Модель Note
После учебного проекта из Первая программа на Laravel:
php artisan make:model Note -m
Разбор:
make:model Note -mсоздает модельNoteи миграцию таблицы за одну команду.- Это ускоряет старт, потому что структура данных и ORM-слой появляются синхронно.
- Дальше модель используется в контроллере и Resource как единый источник данных.
app/Models/Note.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
protected $fillable = ['text'];
}
В миграции — поле text (string). Затем:
php artisan migrate
Разбор:
migrateприменяет все непримененные миграции и создает/обновляет таблицы.- Для
Noteэто означает, что таблица физически появляется в базе и готова к CRUD. - Без этой команды API вернет ошибки уровня
SQLSTATEпри попытке чтения или записи.
$fillable разрешает массовое заполнение Note::create(['text' => '…']) — без этого Laravel проигнорирует поле из соображений безопасности.
Установка Sanctum
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Разбор:
composer requireдобавляет пакет Sanctum в проект и подтягивает его зависимости.vendor:publishкопирует конфигурацию и миграции Sanctum в проект, чтобы их можно было контролировать.- Повторный
migrateсоздает таблицы токенов (personal_access_tokens) для хранения хэшей ключей доступа.
Миграция создаёт таблицу personal_access_tokens — там хранятся хэши токенов.
app/Models/User.php:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Разбор:
- Трейт
HasApiTokensдобавляет моделиUserметоды для создания и управления API-токенами. createToken(...)возвращаетplainTextTokenтолько один раз, поэтому клиент должен сохранить его сразу.- Это стандартная интеграция Sanctum для Bearer-аутентификации в API.
Трейт HasApiTokens добавляет метод $user->createToken('имя')->plainTextToken.
В Laravel 11+ маршруты API часто уже защищены middleware auth:sanctum в группе в routes/api.php.
API Resource — форма JSON
Модель Note в БД может иметь десятки полей. В API клиенту отдают только нужные — через Resource:
php artisan make:resource NoteResource
Разбор:
- Команда создает класс Resource, который отвечает за JSON-представление модели.
- Resource отделяет внутреннюю структуру БД от публичного API-контракта.
- Это делает API стабильнее при будущих изменениях модели.
Код ITЗагрузка примера кода…
Разбор:
toArray(Request $request)определяет точный набор полей, которые попадут в JSON-ответ.created_at?->toIso8601String()безопасно форматирует дату, даже если полеnull.- Такой слой особенно полезен, когда нужно скрывать внутренние поля и отдавать только бизнес-значимые данные.
$this внутри Resource — обёртка над моделью Note. ?-> — безопасный вызов, если дата null.
NoteResource::collection($notes) превращает коллекцию в JSON-массив объектов одинаковой структуры.
Контроллер API
php artisan make:controller Api/NoteController --api
Разбор:
--apiгенерирует контроллер без HTML-методовcreate/edit, оставляя API-ориентированные действия.- Это подчеркивает разделение между веб-интерфейсом и JSON API.
- Такой контроллер проще держать чистым и узкоспециализированным.
Флаг --api создаёт методы без create/edit для HTML-форм.
Код ITЗагрузка примера кода…
Разбор:
index()возвращает коллекцию черезNoteResource::collection(...), сохраняя единый формат JSON.store()валидирует вход, создает запись и отдает созданный объект в том же формате Resource.destroy()удаляет запись и отвечает204 No Content, что соответствует REST-практике для DELETE.- Типизация
destroy(Note $note)использует route model binding и сокращает ручной код поиска.
validate при ошибке вернёт JSON 422 с описанием полей. noContent() — ответ 204 без тела (стандарт для DELETE).
Маршруты и логин
routes/api.php:
Код ITЗагрузка примера кода…
Разбор:
- Публичный
/loginendpoint принимает email/password, валидирует и выдает токен при успешной проверке. Hash::check(...)сравнивает введенный пароль с хэшем в базе, не раскрывая исходный пароль.- Группа
Route::middleware('auth:sanctum')защищает CRUD-маршруты и требует Bearer-токен. Route::apiResource(...)->only([...])ограничивает набор операций и делает API более предсказуемым.
Разбор логина
validate— email и password обязательны.User::where('email', …)->first()— ищем пользователя.Hash::check($plain, $user->password)— сравнение с хэшем в БД (пароль в открытом виде в БД не хранится).createToken('api')— новая запись вpersonal_access_tokens;plainTextTokenпоказывается один раз клиенту.
Защищённая группа: middleware auth:sanctum читает Bearer-токен и подставляет $request->user().
Создайте пользователя: php artisan tinker → User::factory()->create(['email' => 'user@example.com', 'password' => bcrypt('password')]).
Проверка через curl
curl -X POST http://127.0.0.1:8000/api/login \
-H "Content-Type: application/json" \
-d "{\"email\":\"user@example.com\",\"password\":\"password\"}"
Разбор:
- Первый
curlимитирует логин-клиент и получает токен доступа в JSON. - Заголовок
Content-Type: application/jsonобязателен для корректного парсинга тела запроса. - Возвращенный токен затем используется как Bearer в остальных запросах.
Ответ: {"token":"1|abcdef…"}. Скопируйте значение token.
curl http://127.0.0.1:8000/api/notes \
-H "Authorization: Bearer 1|abcdef..."
curl -X POST http://127.0.0.1:8000/api/notes \
-H "Authorization: Bearer 1|abcdef..." \
-H "Content-Type: application/json" \
-d "{\"text\":\"Заметка из API\"}"
Разбор:
- Заголовок
Authorization: Bearer ...подтверждает личность клиента для защищенных endpoint-ов. - GET-запрос возвращает список заметок, доступных аутентифицированному пользователю.
- POST-запрос создает новую заметку через тот же API-контракт и проверки валидации.
- Такой набор команд удобно использовать как базовый smoke-тест API без Postman.
Префикс /api Laravel добавляет к маршрутам из routes/api.php автоматически.
Частые ошибки
| Симптом | Причина |
|---|---|
| 419 / CSRF | POST на web.php вместо api.php |
| 401 на все запросы | Нет заголовка Authorization: Bearer или опечатка в токене |
| Пустой JSON | Забыли обернуть в NoteResource |
| 422 на store | Не передали text или превышен max:2000 |
Что попробовать дальше
- Feature-тест:
$this->actingAs($user)->getJson('/api/notes')илиwithToken()— PHPUnit. - Policy: удалять заметку может только автор — Очереди и политики.
- SPA на Vue — хранить токен в
localStorageи передавать вfetch.
Связанные материалы
Безопасное хранение токенов и отзыв доступа
Для мобильных и серверных клиентов токен хранится в защищенном хранилище платформы. Для SPA используют краткоживущие токены и понятный процесс выхода.
Отзыв токена при logout
Route::middleware('auth:sanctum')->post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out']);
});
Разбор:
- Endpoint доступен только аутентифицированному пользователю через
auth:sanctum. currentAccessToken()->delete()отзывает именно текущий токен клиента, а не все токены пользователя.- Это корректный logout для конкретной сессии устройства/приложения.
- Ответ JSON подтверждает успешное завершение API-сессии.
Этот endpoint завершает текущую сессию API-клиента и снижает риск утечки долгоживущих токенов.
Версионирование API
При росте проекта удобно закладывать версию в URL:
/api/v1/notes/api/v2/notes
Подход помогает выпускать новые поля и правила без резкого изменения старых клиентов.
Практика
- Создайте группу
Route::prefix('v1'). - Добавьте
NoteV1Resourceи отдельные feature-тесты. - Подготовьте план миграции для клиентов.
Связанные темы: Livewire, Filament, тестирование в PHP.