Разработка · 5 мин чтения

Laravel Sanctum: как настроить авторизацию для API

Настраивал laravel sanctum авторизацию в последних трёх проектах на бэкенде: под React-панель для клиента, под Telegram-бота на aiogram и под n8n-автоматизацию, которая дёргает внутренний API по расписанию. Каждый раз разработчики путают два разных режима работы пакета - cookie-авторизацию для SPA и токены для внешних клиентов - и в итоге либо ловят дыры в CORS, либо получают бота, который не может достучаться до защищённого роута. Разберу оба сценария на реальных конфигах, покажу, где Sanctum ломается на проде и как читать ошибки 401 и 419, которые он выдаёт чаще всего.

Sanctum, Passport и JWT: что выбрать для авторизации API

Sanctum появился в Laravel как замена Passport для задач, где полноценный OAuth2-сервер избыточен. Если API отдаёт данные только вашему SPA, мобильному приложению или паре внешних сервисов - Sanctum закрывает вопрос за 20-30 минут настройки.

Passport нужен, когда сторонние разработчики должны получать доступ через классический OAuth2 flow с client_id/client_secret, например если вы строите публичную платформу вроде маркетплейса с интеграциями. Самописный JWT я ставлю только когда система полностью stateless и session-таблица в базе противопоказана по архитектуре - это редкий случай на практике.

Критерий Sanctum Passport JWT (tymon/jwt-auth)
Время на настройку 20-40 минут 1-2 часа 1-2 часа + ротация ключей
SPA cookie-режим есть из коробки нет нет
OAuth2 (сторонние клиенты) нет есть нет
Отзыв токена без ожидания истечения мгновенно, через БД мгновенно нужен blacklist
Подходит для ботов и n8n да, через personal access tokens избыточно да, но сложнее в поддержке

Для большинства проектов, которые я делаю - CRM на React, чат-боты с базой знаний, панели администратора - хватает Sanctum. Passport ставил всего пару раз, когда клиент сам хотел выдавать доступ внешним подрядчикам по OAuth2-протоколу.

Установка и базовая настройка Sanctum в Laravel

Начиная с Laravel 11 пакет часто уже входит в стартовый набор, но проверить стоит в любом случае:

composer require laravel/sanctum
php artisan vendor:publish --provider="LaravelSanctumSanctumServiceProvider"
php artisan migrate

Миграция создаёт таблицу personal_access_tokens - туда пишутся все выданные токены с указанием абилок (прав) и временем последнего использования. Дальше в модель User добавляется трейт:

use LaravelSanctumHasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

После этого у модели появляются методы createToken(), tokens() и currentAccessToken(). В config/sanctum.php смотрю два параметра сразу: stateful (список доменов, с которых запросы идут через cookie-сессию) и expiration (время жизни токена в минутах - по умолчанию null, то есть бессрочно, что для боевого проекта почти всегда меняю на конкретное число).

Это тот режим, ради которого Sanctum вообще стоит ставить отдельно от простых Bearer-токенов. Если фронт и бэк живут на одном домене или поддоменах одного домена, авторизация идёт через httpOnly cookie - токен не попадает в JS, а значит XSS не украдёт сессию, как это бывает при хранении JWT в localStorage.

Настройка для связки Laravel API и React-панели, которую я собирал похожим образом для дашборда на Vue.js с BI-метриками, выглядела так:

SANCTUM_STATEFUL_DOMAINS=app.example.ru
SESSION_DOMAIN=.example.ru
SESSION_DRIVER=cookie

В config/cors.php обязательно включаю supports_credentials:

'paths' => ['api/*', 'sanctum/csrf-cookie'],
'supports_credentials' => true,
'allowed_origins' => ['https://app.example.ru'],

На фронте перед логином обязательно нужно сходить на /sanctum/csrf-cookie, чтобы получить CSRF-токен, а axios настроить с withCredentials: true. Забытый запрос за csrf-cookie - причина процентов восьмидесяти обращений «Sanctum не работает», с которыми ко мне приходят на консультацию.

Если фронт и бэк на разных доменах без общего родительского домена, cookie-режим не подойдёт вообще - там остаются только Bearer-токены, о которых ниже.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Токены для мобильных клиентов, ботов и внешних интеграций

Второй режим - классические Bearer-токены, когда клиент не браузер и cookie ему не подходят. Так работает связка с Telegram-ботом на aiogram, который дёргает API для отправки уведомлений, или с n8n-сценарием, который раз в час забирает заказы из базы и кладёт их в таблицу для отчёта.

Выдача токена при логине:

$token = $user->createToken('n8n-integration', ['orders:read'])->plainTextToken;

return response()->json(['token' => $token]);

Токен показывается один раз - в базе хранится только его хеш, поэтому если клиент потерял значение, остаётся только выпустить новый и отозвать старый. Для n8n или aiogram-бота обычно генерирую персональный токен вручную через artisan-команду или сидер, а не через публичный эндпоинт логина - внешним сервисам городить форму входа незачем:

php artisan tinker
>>> $bot = User::find(1);
>>> $bot->createToken('aiogram-notify', ['notify:send'])->plainTextToken

Дальше клиент передаёт токен в заголовке:

Authorization: Bearer 3|kJ8x...token

Абилки токенов и защита роутов

Абилки (abilities) - скоупы, которые ограничивают, что конкретный токен может делать, даже если он привязан к пользователю с полными правами в системе. На практике беру за правило: у каждого внешнего клиента - свой набор прав, минимально нужный для задачи.

Клиент Абилки Что доступно
n8n (выгрузка заказов) orders:read только чтение заказов
aiogram-бот (уведомления) notify:send только отправка сообщений
React-панель (админ) * полный доступ через сессию

Проверка внутри контроллера или Form Request:

Route::middleware('auth:sanctum')->get('/orders', function (Request $request) {
    if (! $request->user()->tokenCan('orders:read')) {
        abort(403);
    }

    return Order::latest()->paginate(20);
});

Роуты закрываются через стандартный middleware auth:sanctum, он одинаково работает и для cookie-сессии, и для Bearer-токена - Sanctum сам определяет механизм по наличию заголовка Authorization. Держу в библиотеке готовых скриптов заготовку middleware, которая логирует каждый вызов API с указанием, каким токеном и с какими абилками он был сделан - удобно, когда нужно быстро найти, кто именно дёрнул эндпоинт лишний раз.

Частые ошибки при настройке Sanctum

За несколько лет работы с пакетом набрался список повторяющихся проблем:

  • 419 Page Expired - фронт не сходил за /sanctum/csrf-cookie перед логином, либо домены в SANCTUM_STATEFUL_DOMAINS не совпадают с реальным доменом фронта (с www или без - уже разные записи).
  • 401 после успешного логина - SESSION_DOMAIN указан без точки перед доменом, из-за чего cookie не расшаривается между поддоменами api.example.ru и app.example.ru.
  • Токен работает в Postman, но не с фронта - не включён supports_credentials в CORS, браузер молча режет cookie.
  • Токен бота вдруг перестал работать - истёк срок жизни, заданный в expiration, а обновление токена никто не реализовал на стороне n8n или aiogram-скрипта.
  • SESSION_DRIVER=file на балансировщике с несколькими серверами - сессия создаётся на одном сервере, а следующий запрос улетает на другой и не находит её. Меняю на database или redis, как только в проекте появляется больше одного инстанса.

Отдельно слежу за очисткой истёкших токенов - таблица personal_access_tokens растёт бесконтрольно, если не повесить плановую задачу с php artisan sanctum:prune-expired --hours=24 в шедулере.

API и серверная часть под SPA

API / Бэкенд

от 100 000 ₽

Подробнее →

Частые вопросы

Нужен ли Sanctum, если API использует только один фронт на том же домене?

Да, если это SPA - React, Vue или Next.js в режиме SPA. Cookie-режим Sanctum как раз для такого сценария и даёт защиту от XSS без ручной реализации CSRF-проверок и сессионной логики.

Можно ли использовать Sanctum вместе с Passport в одном проекте?

Технически да - пакеты не конфликтуют, потому что оба работают через один и тот же guard-механизм Laravel. На практике смысла мало: если часть клиентов требует полноценный OAuth2, эту часть выношу на Passport, а внутренние SPA и боты остаются на Sanctum.

Как отозвать доступ у конкретного токена, не трогая остальные сессии пользователя?

У каждого токена свой ID в таблице personal_access_tokens, поэтому отзыв точечный: $user->tokens()->where('name', 'aiogram-notify')->delete(). Остальные токены и cookie-сессии этого пользователя продолжают работать без изменений.

Подходит ли Sanctum для мобильного приложения на Flutter или React Native?

Да, через Bearer-токены - мобильные клиенты не работают с cookie так, как браузер, поэтому им нужен именно personal access token, полученный при логине и сохранённый в защищённом хранилище устройства.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно