Токены API и ограничение запросов
Мобильное приложение или чужой сервис не могут войти через форму и хранить сессию. Они получают токен — длинную случайную строку — и присылают его в каждом запросе. В Laravel токены выдаёт пакет Sanctum.
Установка
php artisan install:apiКоманда ставит Sanctum, создаёт таблицу токенов и файл routes/api.php. В модель пользователя добавьте трейт:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}Выдача токена
// routes/api.php
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
Route::post('/login', function (Request $request) {
$data = $request->validate([
'email' => 'required|email',
'password' => 'required',
'device' => 'required',
]);
$user = User::where('email', $data['email'])->first();
if (!$user || !Hash::check($data['password'], $user->password)) {
throw ValidationException::withMessages([
'email' => 'Неверная почта или пароль.',
]);
}
return ['token' => $user->createToken($data['device'])->plainTextToken];
});Защищённые маршруты
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn (Request $request) => $request->user());
Route::apiResource('posts', PostController::class);
});Клиент присылает токен в заголовке:
curl https://example.com/api/user -H "Authorization: Bearer 1|Xk3f9aB..." -H "Accept: application/json"Без токена или с неверным токеном ответ — код 401.
Права токена
Токену можно разрешить только часть действий:
$token = $user->createToken('reports', ['posts:read'])->plainTextToken;public function store(Request $request)
{
if (!$request->user()->tokenCan('posts:write')) {
abort(403);
}
// …
}Отзыв
$request->user()->currentAccessToken()->delete(); // выход на этом устройстве
$request->user()->tokens()->delete(); // выход вездеОграничение частоты запросов
Чтобы один клиент не перегрузил сервер, число запросов ограничивают посредником throttle:
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
// не больше 60 запросов в минуту
});При превышении ответ — код 429 с заголовком Retry-After.
Свои правила
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
public function boot(): void
{
RateLimiter::for('uploads', function (Request $request) {
return Limit::perHour(20)->by($request->user()?->id ?: $request->ip());
});
}Route::post('/files', [FileController::class, 'store'])->middleware('throttle:uploads');Метод by() задаёт, по чему считать: по пользователю или по адресу.
Вход — тоже под ограничение
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:5,1');Пять попыток в минуту делают подбор пароля бессмысленным.
Частая ошибка
Значение plainTextToken доступно только в момент создания: в базе хранится хеш, и показать токен повторно нельзя. Отдайте его клиенту сразу. И токен — такой же секрет, как пароль: не записывайте его в журнал, не передавайте в адресе запроса и задайте срок жизни настройкой expiration в config/sanctum.php.