Перейти к основному содержимому

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: 144.md.


Web и API — в чём разница

routes/web.phproutes/api.php
ОтветHTML (Blade)JSON
СессияCookie, CSRF-токен в формахОбычно без cookie-сессии
Префикс URL//api/… (Laravel добавляет автоматически)
Middlewareweb (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 и passwordJSON { "token": "…" }
Запросы с Authorization: Bearer …CRUD заметок

Модель Note

После учебного проекта из 1431:

php artisan make:model Note -m

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

$fillable разрешает массовое заполнение Note::create(['text' => '…']) — без этого Laravel проигнорирует поле из соображений безопасности.


Установка Sanctum

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Миграция создаёт таблицу personal_access_tokens — там хранятся хэши токенов.

app/Models/User.php:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}

Трейт HasApiTokens добавляет метод $user->createToken('имя')->plainTextToken.

В Laravel 11+ маршруты API часто уже защищены middleware auth:sanctum в группе в routes/api.php.


API Resource — форма JSON

Модель Note в БД может иметь десятки полей. В API клиенту отдают только нужные — через Resource:

php artisan make:resource NoteResource
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class NoteResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'text' => $this->text,
'created_at' => $this->created_at?->toIso8601String(),
];
}
}

$this внутри Resource — обёртка над моделью Note. ?-> — безопасный вызов, если дата null.

NoteResource::collection($notes) превращает коллекцию в JSON-массив объектов одинаковой структуры.


Контроллер API

php artisan make:controller Api/NoteController --api

Флаг --api создаёт методы без create/edit для HTML-форм.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\NoteResource;
use App\Models\Note;
use Illuminate\Http\Request;

class NoteController extends Controller
{
public function index()
{
return NoteResource::collection(Note::latest()->get());
}

public function store(Request $request)
{
$data = $request->validate(['text' => 'required|string|max:2000']);
$note = Note::create($data);
return new NoteResource($note);
}

public function destroy(Note $note)
{
$note->delete();
return response()->noContent();
}
}

validate при ошибке вернёт JSON 422 с описанием полей. noContent() — ответ 204 без тела (стандарт для DELETE).


Маршруты и логин

routes/api.php:

use App\Http\Controllers\Api\NoteController;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Route;

Route::post('/login', function (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' => 'Invalid credentials'], 401);
}
$token = $user->createToken('api')->plainTextToken;
return response()->json(['token' => $token]);
});

Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('notes', NoteController::class)->only(['index', 'store', 'destroy']);
});

Разбор логина

  1. validate — email и password обязательны.
  2. User::where('email', …)->first() — ищем пользователя.
  3. Hash::check($plain, $user->password) — сравнение с хэшем в БД (пароль в открытом виде в БД не хранится).
  4. createToken('api') — новая запись в personal_access_tokens; plainTextToken показывается один раз клиенту.

Защищённая группа: middleware auth:sanctum читает Bearer-токен и подставляет $request->user().

Создайте пользователя: php artisan tinkerUser::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\"}"

Ответ: {"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\"}"

Префикс /api Laravel добавляет к маршрутам из routes/api.php автоматически.


Частые ошибки

СимптомПричина
419 / CSRFPOST на web.php вместо api.php
401 на все запросыНет заголовка Authorization: Bearer или опечатка в токене
Пустой JSONЗабыли обернуть в NoteResource
422 на storeНе передали text или превышен max:2000

Что попробовать дальше

  1. Feature-тест: $this->actingAs($user)->getJson('/api/notes') или withToken()PHPUnit.
  2. Policy: удалять заметку может только автор — Очереди и политики.
  3. SPA на Vue — хранить токен в localStorage и передавать в fetch.

Связанные материалы


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).