Passer au contenu principal

Fonctionnement de la façade RateLimiter

La limitation de débit de Laravel repose sur la classe Illuminate\Cache\RateLimiting\Limit et la façade RateLimiter. En interne, un compteur est stocké dans un driver de cache (par défaut fichier ou Redis) pour suivre le nombre de requêtes. Le middleware throttle exécute pour chaque requête la closure définie via RateLimiter::for() ; si la limite est atteinte, il renvoie 429 Too Many Requests.

Définir des limiters personnalisés dans AppServiceProvider

La configuration de la limitation de débit se fait dans la méthode boot() de App\Providers\AppServiceProvider.
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }
}
Le premier argument de RateLimiter::for() est le nom du limiter, référencé depuis le middleware throttle. La closure passée en second argument doit renvoyer une instance de Illuminate\Cache\RateLimiting\Limit.

Limitation par utilisateur, par IP, par plan

Différencier les limites entre utilisateurs authentifiés et invités

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()
        ? Limit::perHour(100)->by($request->user()->id)
        : Limit::perHour(10)->by($request->ip());
});

Limitation selon le plan de l’utilisateur

RateLimiter::for('api', function (Request $request) {
    $user = $request->user();

    if (! $user) {
        return Limit::perMinute(30)->by($request->ip());
    }

    return match ($user->plan) {
        'enterprise' => Limit::none(),
        'pro'        => Limit::perMinute(500)->by($user->id),
        default      => Limit::perMinute(60)->by($user->id),
    };
});

Limitation globale par adresse IP

Applique un throttling par IP, indépendamment de l’endpoint.
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->by($request->ip());
});

Combiner plusieurs limites

Retourner un tableau évalue toutes les limites. Dès qu’une seule est atteinte, 429 est renvoyé.
RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(10)->by($request->ip()),
        Limit::perMinute(5)->by($request->input('email')),
    ];
});
Lorsque vous définissez plusieurs limites avec la même valeur by, préfixez-les pour éviter les collisions de clés.
RateLimiter::for('uploads', function (Request $request) {
    return [
        Limit::perMinute(10)->by('minute:' . $request->user()->id),
        Limit::perDay(1000)->by('day:' . $request->user()->id),
    ];
});

Middleware throttle et nom de limiter

Passez le nom du limiter au middleware throttle.
use Illuminate\Support\Facades\Route;

Route::middleware(['throttle:api'])->group(function () {
    Route::get('/user', function () { /* ... */ });
    Route::post('/posts', function () { /* ... */ });
});

Enregistrement dans bootstrap/app.php

Depuis Laravel 11, les middlewares se configurent dans bootstrap/app.php.
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleApi('api');
    })
    ->create();

Exemple d’application aux routes API

1

Définir les limiters

Définissez plusieurs limiters dans AppServiceProvider.
public function boot(): void
{
    // Accès API général
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });

    // Envoi de fichiers
    RateLimiter::for('uploads', function (Request $request) {
        return $request->user()?->isPro()
            ? Limit::perHour(500)->by($request->user()->id)
            : Limit::perHour(50)->by($request->user()?->id ?: $request->ip());
    });

    // Tentatives de connexion
    RateLimiter::for('login', function (Request $request) {
        return [
            Limit::perMinute(10)->by($request->ip()),
            Limit::perMinute(5)->by($request->input('email')),
        ];
    });
}
2

Appliquer les middlewares aux routes

// routes/api.php
use Illuminate\Support\Facades\Route;

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/user', [\App\Http\Controllers\UserController::class, 'show']);
    Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);
});

Route::middleware(['auth:sanctum', 'throttle:uploads'])->group(function () {
    Route::post('/uploads', [\App\Http\Controllers\UploadController::class, 'store']);
});

Route::middleware(['throttle:login'])->group(function () {
    Route::post('/login', [\App\Http\Controllers\AuthController::class, 'login']);
});

Fonctionnement des en-têtes de réponse (X-RateLimit-*)

Le middleware throttle ajoute automatiquement les informations de limitation aux en-têtes de la réponse.
En-têteDescription
X-RateLimit-LimitNombre de requêtes autorisées
X-RateLimit-RemainingNombre de requêtes restantes
Retry-AfterSecondes avant la prochaine requête possible (uniquement lors du 429)
X-RateLimit-ResetTimestamp UNIX auquel la limite se remet à zéro
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 45
X-RateLimit-Reset: 1717000000
Content-Type: application/json

{
    "message": "Too Many Requests."
}

Réponse personnalisée

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)
        ->by($request->user()?->id ?: $request->ip())
        ->response(function (Request $request, array $headers) {
            return response()->json([
                'message' => 'Limite de requêtes dépassée. Veuillez réessayer plus tard.',
                'retry_after' => $headers['Retry-After'],
            ], 429, $headers);
        });
});

Vérification manuelle avec RateLimiter::attempt()

Sans utiliser le middleware throttle, pour vérifier la limitation à un moment arbitraire dans votre code, utilisez RateLimiter::attempt().
use Illuminate\Support\Facades\RateLimiter;

class SmsController extends Controller
{
    public function send(Request $request): \Illuminate\Http\JsonResponse
    {
        $key = 'sms:' . $request->user()->id;

        $executed = RateLimiter::attempt(
            key: $key,
            maxAttempts: 5,
            callback: function () use ($request) {
                app(SmsService::class)->send(
                    $request->user()->phone,
                    $request->input('message')
                );
            },
            decaySeconds: 3600,  // 1 heure
        );

        if (! $executed) {
            $seconds = RateLimiter::availableIn($key);

            return response()->json([
                'message' => "Limite d'envoi SMS dépassée. Réessayez dans {$seconds} secondes.",
            ], 429);
        }

        return response()->json(['message' => 'SMS envoyé.']);
    }
}

Vérifier et réinitialiser le nombre de tentatives

// Nombre actuel de tentatives
$hits = RateLimiter::attempts($key);

// Secondes avant la prochaine remise à zéro
$seconds = RateLimiter::availableIn($key);

// Vérifier si la limite est atteinte
$tooMany = RateLimiter::tooManyAttempts($key, $maxAttempts = 5);

// Réinitialiser manuellement le compteur (par exemple après une déconnexion)
RateLimiter::clear($key);

Exemple de throttling de connexion

public function login(Request $request): mixed
{
    $key = 'login:' . $request->input('email');

    if (RateLimiter::tooManyAttempts($key, 5)) {
        $seconds = RateLimiter::availableIn($key);

        throw ValidationException::withMessages([
            'email' => "Trop de tentatives de connexion. Réessayez dans {$seconds} secondes.",
        ]);
    }

    if (! Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::hit($key, 300);  // décompte pendant 5 minutes

        throw ValidationException::withMessages([
            'email' => 'Adresse e-mail ou mot de passe incorrect.',
        ]);
    }

    RateLimiter::clear($key);

    return redirect()->intended('/dashboard');
}

Limitation basée sur la réponse

Pour ne compter que certaines réponses, utilisez after(). Exemple : compter uniquement les 404 pour prévenir l’énumération de ressources.
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-lookup', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            return $response->getStatusCode() === 404;
        });
});

Limitation basée sur Redis

Il suffit de passer le driver de cache par défaut à Redis pour que le middleware throttle l’utilise automatiquement.

Configuration du driver Redis

// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
# .env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Utiliser throttleWithRedis

Pour utiliser le middleware de throttling optimisé pour Redis, appelez throttleWithRedis() dans bootstrap/app.php.
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleWithRedis();
    })
    ->create();
Le middleware throttle est alors mappé à la classe ThrottleRequestsWithRedis, qui utilise les opérations atomiques de Redis pour un comptage précis.
Lorsque vous utilisez throttleWithRedis(), assurez-vous que Redis est disponible. Si la connexion à Redis échoue, toutes les requêtes risquent d’être refusées.

Avantages de Redis

  • Scalabilité horizontale — le compteur est partagé entre plusieurs instances de serveur.
  • Haute précision — les opérations atomiques empêchent les conditions de course.
  • Gestion du TTL — la fonctionnalité native d’expiration de Redis supprime automatiquement les compteurs.

Pages associées

Cache

Configuration et utilisation des drivers de cache Laravel, dont Redis.
Dernière modification le 13 juillet 2026