Створення безпечного Laravel API з JWT автентифікацією
Автентифікація JWT (JSON Web Token) у Laravel забезпечує механізм без збереження стану з криптографічним підписом для перевірки споживачів API без серверного зберігання сесій. JWT кодує корисне навантаження — зазвичай ідентифікатор користувача та твердження — у компактний рядок, безпечний для URL, підписаний секретним або RSA-ключем, що дозволяє будь-якому сервісу, який має ключ перевірки, незалежно валідувати токен.
Цей посібник охоплює повну реалізацію JWT-автентифікації в Laravel API за допомогою пакету `tymon/jwt-auth`, включаючи налаштування, конфігурацію моделі, логіку контролера, захист маршрутів, стратегію оновлення токенів та зміцнення безпеки для продакшену. Кожен крок містить технічний контекст, що виходить за рамки поверхневих посібників.
Передумови та припущення щодо середовища
Перед початком переконайтеся в наступному:
- PHP 8.1 або вище (рекомендується PHP 8.2 для Laravel 11)
- Laravel 10 або 11, встановлений через Composer
- Composer 2.x
- MySQL 8.0, PostgreSQL 15 або будь-яка PDO-сумісна база даних
- Налаштований файл `.env` з дійсними обліковими даними `DB_*`
- Базове знайомство з сервісним контейнером Laravel, middleware та Eloquent ORM
Якщо ви розгортаєте цей API у продакшен-середовищі, план VPS Хостингу з повним root-доступом надає вам контроль, необхідний для налаштування PHP-FPM, безпечного управління змінними середовища та правильного встановлення дозволів на файли — все це критично важливо для управління JWT-секретом.
Крок 1: Створення нового проекту Laravel
“`bash
composer create-project laravel/laravel laravel-jwt-api
cd laravel-jwt-api
“`
Перевірте встановлення:
“`bash
php artisan –version
“`
Встановіть ключ вашого застосунку, якщо він не був згенерований автоматично:
“`bash
php artisan key:generate
“`
`APP_KEY` у `.env` відрізняється від JWT-секрету. Обидва є обов’язковими та слугують різним криптографічним цілям — `APP_KEY` захищає зашифровані cookie та дані сесій, тоді як `JWT_SECRET` підписує токени.
Крок 2: Встановлення пакету JWT-автентифікації
Пакет `tymon/jwt-auth` є де-факто стандартом для JWT у Laravel. Встановіть його:
“`bash
composer require tymon/jwt-auth
“`
Опублікуйте конфігурацію пакету:
“`bash
php artisan vendor:publish –provider="TymonJWTAuthProvidersLaravelServiceProvider"
“`
Це створює `config/jwt.php`, який керує TTL токена, TTL оновлення, алгоритмом, поведінкою чорного списку та обов’язковими твердженнями. Уважно перегляньте цей файл — значення за замовчуванням є розумними для розробки, але потребують налаштування для продакшену.
Ключові параметри конфігурації в `config/jwt.php`:
| Параметр | За замовчуванням | Рекомендація для продакшену |
|---|---|---|
| — | — | — |
| `ttl` | 60 хвилин | 15–30 хвилин |
| `refresh_ttl` | 20160 хвилин (2 тижні) | 1440–10080 хвилин |
| `algo` | `HS256` | `RS256` для розподілених систем |
| `blacklist_enabled` | `true` | Має бути `true` |
| `blacklist_grace_period` | 0 секунд | 10–30 секунд для паралельних запитів |
| `required_claims` | `['iss','iat','exp','nbf','sub','jti']` | Зберігайте всі; додайте `aud` для мультитенантних API |
Крок 3: Генерація секретного ключа JWT
“`bash
php artisan jwt:secret
“`
Це додає `JWT_SECRET` до вашого файлу `.env`. Це значення використовується як ключ підпису HMAC-SHA256 для всіх токенів при використанні алгоритму `HS256` за замовчуванням.
Критичні примітки щодо безпеки:
- Ніколи не фіксуйте `.env` у системі контролю версій. Негайно додайте його до `.gitignore`.
- У спільному конвеєрі розгортання вводьте `JWT_SECRET` як змінну середовища через вашу систему CI/CD, а не зберігайте його у файлі.
- Якщо ви ротуєте секрет, всі існуючі токени негайно анулюються. Плануйте вікна ротації відповідно та повідомляйте про них споживачів API.
- Для мікросервісних архітектур, де кілька сервісів повинні перевіряти токени, перейдіть на `RS256`. Згенеруйте пару RSA-ключів, зберігайте приватний ключ на сервісі автентифікації та розповсюджуйте лише публічний ключ серед споживчих сервісів.
Крок 4: Налаштування захисту автентифікації
Відкрийте `config/auth.php` та оновіть розділи defaults та guards:
“`php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
“`
Це вказує системі автентифікації Laravel використовувати JWT-драйвер при розв’язанні захисту `api`. Middleware `auth:api` тепер делегуватиме валідацію токенів до `tymon/jwt-auth` замість Laravel Passport або стандартного драйвера токенів.
Не видаляйте захист `web`. Багато внутрішніх компонентів Laravel залежать від нього, і його видалення спричиняє несподівані збої в консольних командах та обробниках черги, які взаємодіють із системою автентифікації.
Крок 5: Створення моделі користувача та міграції
Якщо стандартна модель `User` та міграція вже існують (вони є у свіжому встановленні Laravel), ви можете змінити їх безпосередньо. Якщо починаєте з нуля:
“`bash
php artisan make:model User -m
“`
Відкрийте файл міграції в `database/migrations/` та визначте схему:
“`php
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
“`
Запустіть міграцію:
“`bash
php artisan migrate
“`
Примітка для продакшену: Завжди запускайте міграції з прапором `–force` у продакшен-середовищах, де `APP_ENV=production`, оскільки Laravel запитуватиме підтвердження в іншому випадку:
“`bash
php artisan migrate –force
“`
Крок 6: Реалізація інтерфейсу JWTSubject у моделі користувача
Пакет `tymon/jwt-auth` вимагає, щоб ваша автентифікована модель реалізовувала контракт `JWTSubject`. Відкрийте `app/Models/User.php`:
“`php
<?php
namespace AppModels;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateFoundationAuthUser as Authenticatable;
use IlluminateNotificationsNotifiable;
use TymonJWTAuthContractsJWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
/**
- Get the identifier that will be stored in the JWT subject claim.
*/
public function getJWTIdentifier(): mixed
{
return $this->getKey();
}
/**
- Return a key-value array of arbitrary claims to add to the JWT payload.
*/
public function getJWTCustomClaims(): array
{
return [];
}
}
“`
Про власні твердження: Метод `getJWTCustomClaims()` — це місце, де ви вбудовуєте специфічні для застосунку дані безпосередньо в корисне навантаження токена. Поширені випадки використання включають вбудовування `role`, `tenant_id` або `permissions`, щоб нижчестоящі сервіси могли приймати рішення про авторизацію без звернення до бази даних. Будьте обережні щодо того, що ви вбудовуєте — кожне твердження збільшує розмір токена та є читабельним для будь-кого, хто base64-декодує корисне навантаження. Ніколи не вбудовуйте конфіденційні дані, такі як паролі або персональні дані.
Крок 7: Побудова контролера автентифікації
“`bash
php artisan make:controller AuthController
“`
Заповніть `app/Http/Controllers/AuthController.php` повною логікою автентифікації:
“`php
<?php
namespace AppHttpControllers;
use AppModelsUser;
use IlluminateHttpJsonResponse;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use IlluminateSupportFacadesHash;
use IlluminateValidationValidationException;
use TymonJWTAuthExceptionsJWTException;
use TymonJWTAuthFacadesJWTAuth;
class AuthController extends Controller
{
/**
- Register a new user and return a JWT.
*/
public function register(Request $request): JsonResponse
{
$validated = $request->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 = JWTAuth::fromUser($user);
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
], 201);
}
/**
- Authenticate a user and return a JWT.
*/
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
]);
if (!$token = Auth::guard('api')->attempt($credentials)) {
return response()->json(['error' => 'Invalid credentials'], 401);
}
return $this->respondWithToken($token);
}
/**
- Invalidate the current token (logout).
*/
public function logout(): JsonResponse
{
try {
Auth::guard('api')->logout();
} catch (JWTException $e) {
return response()->json(['error' => 'Failed to invalidate token'], 500);
}
return response()->json(['message' => 'Successfully logged out']);
}
/**
- Refresh the current token.
*/
public function refresh(): JsonResponse
{
try {
$token = Auth::guard('api')->refresh();
} catch (JWTException $e) {
return response()->json(['error' => 'Token cannot be refreshed'], 401);
}
return $this->respondWithToken($token);
}
/**
- Return the authenticated user's profile.
*/
public function me(): JsonResponse
{
return response()->json(Auth::guard('api')->user());
}
/**
- Format the token response consistently.
*/
protected function respondWithToken(string $token): JsonResponse
{
return response()->json([
'token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
]);
}
}
“`
Чому явне визначення захисту має значення: Виклик `Auth::attempt()` без вказівки захисту повертається до захисту за замовчуванням. Якщо ви змінили значення за замовчуванням на `api`, це працює — але є нестабільним. Завжди викликайте `Auth::guard('api')->attempt()` явно, щоб уникнути непомітних помилок при зміні захисту за замовчуванням під час рефакторингу.
Крок 8: Визначення маршрутів API
Відкрийте `routes/api.php` та визначте маршрути автентифікації та захищені маршрути:
“`php
<?php
use AppHttpControllersAuthController;
use IlluminateSupportFacadesRoute;
// Public authentication routes
Route::prefix('auth')->group(function () {
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
});
// Protected routes — require a valid JWT
Route::middleware('auth:api')->prefix('auth')->group(function () {
Route::post('logout', [AuthController::class, 'logout']);
Route::post('refresh', [AuthController::class, 'refresh']);
Route::get('me', [AuthController::class, 'me']);
});
// Example protected resource routes
Route::middleware('auth:api')->group(function () {
Route::apiResource('posts', AppHttpControllersPostController::class);
});
“`
Стратегія префіксів маршрутів: Групування кінцевих точок автентифікації під `/api/auth/` є широко прийнятою конвенцією, яка робить документацію API чистішою та спрощує правила обмеження швидкості — ви можете застосовувати суворіше обмеження до `/api/auth/login` незалежно від кінцевих точок ресурсів.
Крок 9: Захист маршрутів та обробка збоїв middleware
Middleware `auth:api` Laravel повертатиме відповідь `401 Unauthenticated`, коли токен відсутній, прострочений або недійсний. Щоб повертати узгоджену JSON-відповідь замість HTML-перенаправлення, перевизначте метод `unauthenticated` у `app/Exceptions/Handler.php`:
“`php
use IlluminateAuthAuthenticationException;
use IlluminateHttpRequest;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson() || $request->is('api/*')) {
return response()->json(['error' => 'Unauthenticated'], 401);
}
return redirect()->guest(route('login'));
}
“`
Без цього перевизначення API-клієнти отримують HTML-перенаправлення 302 на сторінку входу, яка не існує в чистому API-застосунку — поширене джерело плутанини під час інтеграційного тестування.
Крок 10: Стратегія оновлення токенів та чорний список
Безстанова природа JWT створює протиріччя: токени є самодостатніми та дійсними до закінчення терміну дії, але вам потрібен спосіб їх відкликання при виході. Пакет `tymon/jwt-auth` вирішує це за допомогою чорного списку токенів, підкріпленого драйвером кешу Laravel.
Як працює чорний список:
- При виході твердження `jti` (JWT ID) токена зберігається в кеші з TTL, що відповідає часу, що залишився до закінчення терміну дії токена.
- При кожному автентифікованому запиті middleware перевіряє чорний список перед прийняттям токена.
- Налаштування `blacklist_grace_period` дозволяє короткий проміжок часу, протягом якого токен, що оновлюється, все ще може використовуватися, запобігаючи гонкам стану в клієнтах, які роблять паралельні запити.
Переконайтеся, що ваш драйвер кешу підтримує це. Стандартний драйвер `file` працює для розгортань на одному сервері. Для горизонтально масштабованих API, що працюють на кількох вузлах — що є звичайним при використанні Виділених серверів у конфігурації з балансуванням навантаження — перейдіть на Redis або Memcached:
“`env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
“`
Процес оновлення токена:
“`
Client API Server
| — POST /api/auth/refresh ——> | |
| Authorization: Bearer <old> | |
| — Validate old token | |
| — Blacklist old token's jti | |
| — Issue new token | |
| <– 200 { token: <new> } ——– |
“`
Старий токен негайно вноситься до чорного списку після оновлення. Клієнти повинні зберігати новий токен та відкидати старий. Реалізуйте це як перехоплювач Axios або еквівалент у вашому фронтенді для прозорого оновлення токенів.
Крок 11: Тестування API
Використовуйте `curl` або Postman для перевірки кожної кінцевої точки.
Реєстрація користувача:
“`bash
curl -X POST https://your-domain.com/api/auth/register
-H "Content-Type: application/json"
-d '{
"name": "Jane Smith",
"email": "jane@example.com",
"password": "SecurePass123!",
"password_confirmation": "SecurePass123!"
}'
“`
Очікувана відповідь (`201 Created`):
“`json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…",
"token_type": "bearer",
"expires_in": 3600
}
“`
Вхід:
“`bash
curl -X POST https://your-domain.com/api/auth/login
-H "Content-Type: application/json"
-d '{"email": "jane@example.com", "password": "SecurePass123!"}'
“`
Доступ до захищеного маршруту:
“`bash
curl -X GET https://your-domain.com/api/auth/me
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…"
“`
Оновлення токена:
“`bash
curl -X POST https://your-domain.com/api/auth/refresh
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…"
“`
Вихід:
“`bash
curl -X POST https://your-domain.com/api/auth/logout
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…"
“`
JWT проти сесій проти Laravel Sanctum проти Passport
Розуміння того, коли JWT є правильним вибором, вимагає порівняння його з альтернативами, доступними в екосистемі Laravel.
| Критерій | JWT (`tymon/jwt-auth`) | Laravel Sanctum | Laravel Passport | На основі сесій |
|---|---|---|---|---|
| — | — | — | — | — |
| **Стан** | Без стану | Без стану (SPA-токени) / Зі станом (cookie) | Зі станом (токени в БД) | Зі станом |
| **Зберігання токенів** | На стороні клієнта | На стороні клієнта або cookie | База даних | Серверна сесія |
| **Відкликання** | Чорний список (кеш) | Негайне (видалення з БД) | Негайне (видалення з БД) | Негайне |
| **Масштабованість** | Відмінна (без звернення до БД на запит) | Хороша | Помірна (звернення до БД на запит) | Погана (потрібна синхронізація сесій) |
| **Підтримка OAuth2** | Ні | Ні | Так (повний OAuth2-сервер) | Ні |
| **Складність** | Середня | Низька | Висока | Низька |
| **Найкраще для** | Безстанові API, мікросервіси | SPA, мобільні застосунки | Сторонні OAuth-клієнти | Традиційні веб-застосунки |
| **Інтроспекція токенів** | Декодування корисного навантаження на стороні клієнта | Потребує виклику API | Потребує виклику API | Н/Д |
Коли обирати JWT замість Sanctum: Якщо ваш API споживається сторонніми клієнтами, мобільними застосунками або мікросервісами, які не можуть спільно використовувати сесійний cookie або підключення до бази даних з вашим Laravel-застосунком, самодостатня природа JWT є значною перевагою. Якщо ви створюєте власний SPA на тому ж домені, Sanctum з автентифікацією на основі cookie є простішим і повністю уникає проблем безпеки зберігання токенів.
Зміцнення безпеки для продакшену
Робоча реалізація JWT не є безпечною за замовчуванням. Застосуйте ці заходи зміцнення перед запуском:
1. Безумовно застосовуйте HTTPS
JWT-токени, що передаються через HTTP, легко перехоплюються. Застосовуйте TLS на рівні веб-сервера та перенаправляйте весь HTTP-трафік. Поєднайте це з SSL-сертифікатом, щоб забезпечити зашифрований транспорт для кожного API-запиту.
2. Встановіть агресивні TTL токенів
Короткоживучі токени доступу (15–30 хвилин) у поєднанні з довгоживучими токенами оновлення (7–14 днів) обмежують наслідки викраденого токена. Оновіть `config/jwt.php`:
“`php
'ttl' => 15,
'refresh_ttl' => 10080,
“`
3. Застосовуйте обмеження швидкості до кінцевих точок автентифікації
Вбудований middleware throttle Laravel запобігає атакам грубої сили:
“`php
Route::middleware(['throttle:10,1'])->group(function () {
Route::post('auth/login', [AuthController::class, 'login']);
Route::post('auth/register', [AuthController::class, 'register']);
});
“`
Це обмежує кожну IP-адресу до 10 запитів на хвилину на кінцевих точках автентифікації.
4. Валідуйте твердження `aud` для мультитенантних API
Якщо ваш API обслуговує кілька клієнтських застосунків, вбудовуйте та валідуйте твердження `audience`, щоб запобігти повторному використанню токенів між сервісами:
“`php
// In getJWTCustomClaims()
return [
'aud' => config('app.jwt_audience'),
];
“`
5. Захистіть JWT_SECRET на рівні ОС
Встановіть обмежувальні дозволи на файл `.env`:
“`bash
chmod 640 .env
chown www-data:www-data .env
“`
На правильно налаштованому VPS з cPanel, ви можете керувати власністю файлів та дозволами через Файловий менеджер або SSH без ризику розкриття секретів іншим користувачам системи.
6. Реєструйте події автентифікації
Інтегруйте систему подій Laravel для реєстрації невдалих спроб входу, оновлень токенів та виходів у централізований сервіс логування. Це є необхідним для виявлення аномалій.
Додавання контролю доступу на основі ролей
Власні твердження спрощують вбудовування ролей безпосередньо в токен:
“`php
// In User model
public function getJWTCustomClaims(): array
{
return [
'role' => $this->role, // e.g., 'admin', 'editor', 'viewer'
];
}
“`
Створіть middleware для застосування вимог до ролей:
“`php
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use TymonJWTAuthFacadesJWTAuth;
class RoleMiddleware
{
public function handle(Request $request, Closure $next, string $role): mixed
{
$payload = JWTAuth::parseToken()->getPayload();
if ($payload->get('role') !== $role) {
return response()->json(['error' => 'Forbidden'], 403);
}
return $next($request);
}
}
“`
Зареєструйте його в `app/Http/Kernel.php` (Laravel 10) або `bootstrap/app.php` (Laravel 11) та застосуйте до маршрутів:
“`php
Route::middleware(['auth:api', 'role:admin'])->group(function () {
Route::apiResource('admin/users', AdminUserController::class);
});
“`
Застереження: Дані ролей, вбудовані в токен, є актуальними лише на момент видачі токена. Якщо роль користувача змінюється, старий токен продовжує надавати стару роль до його закінчення або оновлення. Для змін ролей з високим рівнем безпеки (наприклад, негайне відкликання прав адміністратора) поєднуйте внесення токена до чорного списку з коротким TTL або виконуйте перевірку ролі в базі даних у middleware разом із перевіркою твердження.
Міркування щодо розгортання для Laravel JWT API
При переході від локальної розробки до продакшен-сервера кілька специфічних для середовища факторів впливають на поведінку JWT:
- Узгодженість часових поясів: Твердження JWT `iat`, `nbf` та `exp` є Unix-мітками часу. Переконайтеся, що часовий пояс вашого сервера встановлено на UTC (`date.timezone = UTC` у `php.ini`), щоб запобігти відхиленню токенів через розбіжність годинників.
- OPcache: Увімкніть PHP OPcache, щоб зменшити накладні витрати на завантаження файлів бібліотеки JWT при кожному запиті. Це особливо помітно на API з великим трафіком.
- Обробники черги для очищення токенів: Якщо ви реалізуєте власні завдання очищення чорного списку токенів, переконайтеся, що ваш обробник черги працює як контрольований процес (Supervisor або systemd).
- Управління змінними середовища: На Панелях керування VPS використовуйте менеджер змінних середовища панелі, а не редагуйте `.env` безпосередньо в продакшені, щоб уникнути випадкового перезапису під час розгортань.
Контрольний список рішень перед запуском
Використовуйте цей контрольний список для перевірки готовності вашої реалізації до продакшену:
- `JWT_SECRET` містить щонайменше 32 символи, є випадково згенерованим та не зафіксованим у системі контролю версій
- `blacklist_enabled` встановлено на `true` у `config/jwt.php`
- TTL токена становить 30 хвилин або менше для токенів доступу
- TTL оновлення встановлено на значення, відповідне вашій політиці сесій
- Всі кінцеві точки API обслуговуються виключно через HTTPS
- Обмеження швидкості застосовано до кінцевих точок `/login` та `/register`
- Обробник виключень `unauthenticated` повертає JSON, а не HTML-перенаправлення
- Власні твердження не містять паролів, секретів або конфіденційних персональних даних
- Драйвер кешу є Redis або Memcached (не `file`) у розгортаннях на кількох серверах
- Події автентифікації реєструються та відстежуються
- Зміни ролей, що потребують негайного ефекту, обробляються через внесення до чорного списку, а не лише через закінчення терміну дії твердження
- Дозволи на файл `.env` обмежені для користувача веб-сервера
Поширені запитання
У чому різниця між `JWT_SECRET` та `APP_KEY` у Laravel?
`APP_KEY` використовується службою шифрування Laravel для шифрування cookie, даних сесій та значень, що передаються через `Crypt::encrypt()`. `JWT_SECRET` використовується виключно `tymon/jwt-auth` для підпису та перевірки JSON Web Token. Вони є криптографічно незалежними та слугують абсолютно різним цілям. Обидва повинні зберігатися в таємниці.
Чому мій JWT-токен продовжує повертати 401, хоча термін його дії ще не закінчився?
Найпоширеніші причини: токен внесено до чорного списку (наприклад, після виходу або оновлення), `JWT_SECRET` було ротовано після видачі токена, драйвер кешу, що зберігає чорний список, недоступний, або є розбіжність годинників між сервером видачі та сервером валідації, що перевищує налаштування `leeway` у `config/jwt.php`. Перевіряйте кожну з цих причин по черзі.
Чи можу я використовувати JWT-автентифікацію з чергами Laravel або консольними командами?
JWT призначений для автентифікації HTTP-запитів. Всередині завдань черги або команд Artisan немає контексту HTTP-запиту, тому ви не можете розв’язати користувача з токена через middleware. Натомість передайте первинний ключ користувача як параметр завдання та завантажте модель безпосередньо за допомогою `User::find($userId)`.
Як обробляти паралельні запити під час оновлення токена без отримання помилок 401?
Встановіть `blacklist_grace_period` у `config/jwt.php` на значення від 10 до 30 секунд. Протягом цього вікна токен, який щойно був оновлений (і технічно внесений до чорного списку), все ще прийматиметься. Це запобігає гонкам стану в клієнтах, які надсилають кілька одночасних запитів під час виконання оновлення.
Чи сумісний `tymon/jwt-auth` з Laravel 11?
Станом на поточний цикл випусків, `tymon/jwt-auth` версії `2.x` підтримує Laravel 10 та 11 через гілку `dev-develop` або теговані випуски, що декларують сумісність. Завжди перевіряйте обмеження `composer.json` пакету та трекер проблем GitHub перед оновленням версій Laravel у проекті, що залежить від цього пакету. Розгляньте можливість фіксації версії пакету у `composer.json`, щоб уникнути несподіваних критичних змін під час `composer update`.
