martes, 17 de junio de 2025

Multiple Rate Limiters en Laravel

En Laravel, "Multiple Rate Limits" (Múltiples Límites de Tasa) se refiere a la capacidad de aplicar diferentes reglas de limitación de solicitudes a diferentes partes de tu aplicación, o incluso a diferentes tipos de usuarios, en lugar de tener un único límite global para todo.

Es una evolución del concepto básico de "Rate Limiting" (limitación de tasa), que es la práctica de restringir el número de solicitudes que un usuario, una dirección IP o un token puede hacer a tu servidor dentro de un período de tiempo determinado.

¿Por qué necesitamos Múltiples Límites de Tasa?

Un límite de tasa único no siempre es suficiente porque las necesidades y los patrones de uso varían:

  • Diferentes tipos de usuarios: Un usuario autenticado quizás necesite (o se le permita) hacer más solicitudes que un usuario invitado. Un usuario "premium" o un administrador podrían tener límites aún más altos.

  • Diferentes endpoints: Las llamadas a una API que consulta una base de datos pesada podrían necesitar un límite más estricto que las llamadas a una API que solo devuelve datos estáticos.

  • Seguridad específica: El endpoint de inicio de sesión necesita un límite muy estricto para prevenir ataques de fuerza bruta, mientras que otras rutas pueden tener límites más generosos.

  • Gestión de recursos: Controlar la carga del servidor limitando el acceso a funcionalidades intensivas en recursos.

¿Cómo funciona en Laravel?

Laravel maneja los límites de tasa a través de su middleware throttle y la fachada RateLimiter. La clave para los "Múltiples Límites de Tasa" reside en el método for() de la fachada RateLimiter, que se define típicamente en el método boot() de tu app/Providers/RouteServiceProvider.php.

Pasos Clave:

  1. Definir los "Límites de Tasa" (Rate Limiters) Nombrados:
    En app/Providers/RouteServiceProvider.php, utilizas RateLimiter::for() para definir distintos límites, cada uno con un nombre único.

  2. Aplicar los Límites a las Rutas:
    Luego, en tus archivos de ruta (routes/web.php o routes/api.php), aplicas el middleware throttle especificando el nombre del límite de tasa que quieres usar.

Ejemplo Detallado:

Vamos a definir varios límites de tasa y aplicarlos a diferentes escenarios.

1. Definición de Límites de Tasa en app/Providers/RouteServiceProvider.php:

PHP

<?php


namespace App\Providers;


use Illuminate\Cache\RateLimiting\Limit;

use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\RateLimiter; // Importa la fachada


class RouteServiceProvider extends ServiceProvider

{

    // ... otras propiedades y métodos ...


    /**

     * Define the rate limiters for the application.

     */

    protected function configureRateLimiting(): void

    {

        // 1. Límite por defecto (para invitados o usuarios generales)

        // 60 peticiones por minuto por dirección IP

        RateLimiter::for('global', function (Request $request) {

            return Limit::perMinute(60)->by($request->ip());

        });


        // 2. Límite para usuarios autenticados

        // 100 peticiones por minuto por ID de usuario autenticado

        RateLimiter::for('authenticated_user', function (Request $request) {

            return Limit::perMinute(100)->by($request->user()?->id ?: $request->ip());

            // Si el usuario no está logueado, se fallbackea a IP

        });


        // 3. Límite estricto para intentos de inicio de sesión

        // 5 peticiones por minuto por IP para prevenir fuerza bruta

        RateLimiter::for('login_attempts', function (Request $request) {

            return Limit::perMinute(5)->by($request->ip());

        });


        // 4. Límite para un endpoint de API específico y sensible

        // 10 peticiones por minuto por usuario para la creación de posts

        RateLimiter::for('create_post_api', function (Request $request) {

            return Limit::perMinute(10)->by($request->user()->id);

        });


        // 5. Límite con response personalizado

        // 3 peticiones por minuto por IP, con un mensaje de error específico

        RateLimiter::for('custom_message', function (Request $request) {

            return Limit::perMinute(3)->by($request->ip())->response(function () {

                return response('Demasiadas solicitudes! Intenta de nuevo en un minuto.', 429);

            });

        });

    }

}


Explicación de la definición:

  • RateLimiter::for('nombre_del_limite', function (Request $request) { ... });: Define un nuevo limitador de tasa con un nombre único (global, authenticated_user, etc.). La closure recibe la instancia de Request.

  • Limit::perMinute(X) / perHour(X) / perDay(X): Especifica el número máximo de solicitudes (X) permitidas dentro de un período de tiempo (por minuto, por hora, por día).

  • ->by($request->ip()): Define cómo se rastrea el límite. En este caso, por la dirección IP. También puedes usar ->by($request->user()->id) si el usuario está autenticado, o cualquier otra cadena única (ej., ->by($request->header('X-API-Key'))).

  • ->response(function () { ... }): Permite definir una respuesta HTTP personalizada (código de estado, mensaje) cuando el límite es excedido. Por defecto, Laravel devuelve un 429 Too Many Requests.


2. Aplicación de Límites de Tasa a las Rutas (ej. routes/api.php o routes/web.php):

PHP

<?php


use Illuminate\Support\Facades\Route;

use App\Http\Controllers\AuthController;

use App\Http\Controllers\PostController;


// Rutas sin middleware de throttle (solo el global por defecto si se aplica globalmente)


// Aplicar el límite 'global' a todas las rutas de API

// Puedes aplicar el middleware 'throttle:global' a todo el archivo si lo deseas.

Route::middleware('throttle:global')->group(function () {


    // Ruta de inicio de sesión con límite estricto

    Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:login_attempts');


    // Rutas para usuarios no autenticados, quizás un poco menos estrictas que el login

    Route::get('/public-data', function () {

        return ['message' => 'Datos públicos accesibles.'];

    })->middleware('throttle:custom_message'); // Usamos el límite con mensaje personalizado


    // Grupo de rutas para usuarios autenticados

    Route::middleware('auth:sanctum', 'throttle:authenticated_user')->group(function () {


        Route::get('/user-profile', function (Request $request) {

            return $request->user();

        });


        // Endpoint de creación de posts con un límite más estricto aún

        Route::post('/posts', [PostController::class, 'store'])->middleware('throttle:create_post_api');


        Route::get('/posts', [PostController::class, 'index']);

    });

});


Explicación de la aplicación:

  • El middleware throttle se aplica a las rutas o grupos de rutas.

  • Cuando se usa, especificas el nombre del limitador de tasa que definiste en RouteServiceProvider (ej., throttle:global, throttle:login_attempts).

  • Puedes encadenar múltiples middleware, incluyendo varios throttle si quisieras, pero lo más común es tener un solo throttle que apunte al limitador de tasa nombrado adecuado.

¿Cómo funciona el "Multiple Rate Limits" en la práctica?

  1. Solicitud Entrante: Una solicitud llega a tu aplicación Laravel.

  2. Middleware throttle: La solicitud pasa a través del middleware throttle configurado para esa ruta.

  3. Identificación del Limitador: El middleware identifica el limitador de tasa nombrado que debe usar (ej., login_attempts).

  4. RateLimiter en Acción: Internamente, la fachada RateLimiter consulta su "almacén" (por defecto, el caché de Laravel, que debe ser persistente como Redis o Memcached en producción) para ver cuántas solicitudes ha hecho el identificador (by()) dentro del período de tiempo (perMinute()).

  5. Verificación del Límite:

    • Si el número de solicitudes es menor o igual al límite, la solicitud procede normalmente.

    • Si el número de solicitudes excede el límite, Laravel detiene la solicitud y devuelve una respuesta 429 Too Many Requests (o la respuesta personalizada si la definiste con ->response()).

Beneficios Clave:

  • Control Granular: Puedes especificar límites de uso precisos para diferentes funcionalidades y usuarios.

  • Mejora de la Seguridad: Defensas específicas contra ataques de fuerza bruta, intentos de enumeración y abuso de API.

  • Optimización de Recursos: Protege tu servidor de sobrecargas al limitar el tráfico a endpoints que consumen muchos recursos.

  • Experiencia de Usuario: Puedes informar a los usuarios sobre los límites de manera más específica, en lugar de un mensaje genérico.

Al aprovechar el sistema de "Multiple Rate Limits" de Laravel, puedes construir aplicaciones más robustas, seguras y eficientes.

¿Se puede devolver un array de Limit desde la definición/configuración del RateLimiter?


No, no se puede devolver directamente un array de objetos Limit desde la closure de definición de un RateLimiter::for().

La closure que pasas al método RateLimiter::for() siempre debe devolver una única instancia de Illuminate\Cache\RateLimiting\Limit.

¿Cómo se manejan entonces los escenarios con "múltiples límites" para una misma acción?

Laravel ofrece dos formas principales de lograr este concepto de "múltiples límites" para un mismo limitador de tasa nombrado:

  1. Encadenando el método then() o and():
    Puedes encadenar múltiples reglas de limitación dentro de la misma definición de Limit usando los métodos then() o and().

    • then(Limit::per...) (Límites Secuenciales):
      Esto significa "aplica el primer límite, y si ese se satisface, aplica el segundo límite". El segundo límite solo se verifica si el primero no se ha excedido. Es útil para límites escalonados (por ejemplo, 100 peticiones por minuto, y si se excede, luego 1000 peticiones por hora).

    • PHP

// Ejemplo: 100 peticiones/minuto, y luego 1000 peticiones/hora

RateLimiter::for('api_heavy_user', function (Request $request) {

    return Limit::perMinute(100)->by($request->user()->id ?: $request->ip())

                ->then(Limit::perHour(1000)->by($request->user()->id ?: $request->ip()));

});



  • and(Limit::per...) (Límites Simultáneos):
    Esto significa "aplica ambos límites simultáneamente". La solicitud solo se permitirá si ambos límites no se han excedido. Es decir, si se excede el límite por minuto O el límite por hora, la solicitud será denegada.

  • PHP

// Ejemplo: No más de 10 peticiones/minuto Y no más de 50 peticiones/hora

RateLimiter::for('strict_api_access', function (Request $request) {

    return Limit::perMinute(10)->by($request->user()->id ?: $request->ip())

                ->and(Limit::perHour(50)->by($request->user()->id ?: $request->ip()));

});



  1. En ambos casos (then o and), lo que se devuelve es una única instancia de Limit que internamente maneja las múltiples condiciones.

  2. Definiendo Múltiples Limitadores de Tasa Nombrados por Separado:
    Esta es la forma que te expliqué en la respuesta anterior para "Multiple Rate Limits". Aquí, defines limitadores de tasa completamente distintos para diferentes escenarios o tipos de usuarios, y luego aplicas el middleware throttle con el nombre adecuado a la ruta o grupo de rutas relevante.

  3. PHP

// En RouteServiceProvider.php

RateLimiter::for('guest_api', function (Request $request) {

    return Limit::perMinute(60)->by($request->ip());

});


RateLimiter::for('auth_api', function (Request $request) {

    return Limit::perMinute(100)->by($request->user()->id);

});


// En routes/api.php

Route::middleware('throttle:guest_api')->group(function () {

    Route::get('/public-resource', function () { /* ... */ });

});


Route::middleware('auth:sanctum', 'throttle:auth_api')->group(function () {

    Route::get('/private-resource', function () { /* ... */ });

});


  1. En este caso, no estamos devolviendo un array, sino definiendo diferentes limitadores de tasa con diferentes nombres, cada uno devolviendo una única instancia de Limit.

En conclusión:

Laravel espera una única instancia de Limit de la closure de RateLimiter::for(). Para combinar reglas, utilizas los métodos then() o and() encadenados a esa instancia de Limit. Para aplicar reglas completamente diferentes a distintos grupos de rutas o usuarios, defines múltiples limitadores de tasa con nombres únicos.


No hay comentarios: