Cargando...

Rate Limiting por IP en ASP.NET Core (.NET 8): 429 Too Many Requests

Limita solicitudes por IP con el rate limiting integrado de .NET 8 y devuelve 429 con Retry-After para proteger tu API del abuso.

En APIs públicas, uno de los problemas más comunes son los picos de solicitudes (request spikes). Un bot, un cliente con errores o una lógica de reintentos demasiado agresiva puede generar muchísimas peticiones en poco tiempo. Una de las primeras capas de defensa es aplicar rate limiting: permitir solo cierto número de solicitudes en una ventana de tiempo.

Con .NET 8, esto se puede implementar de forma moderna, sin escribir un middleware propio, usando el middleware de rate limiting integrado. En este ejemplo aplicamos un límite por IP y, cuando se excede, devolvemos 429 Too Many Requests. Además añadimos el header Retry-After para indicar al cliente cuándo debería reintentar.


Objetivo


Program.cs (Fixed Window Rate Limiting por IP)

Esta configuración permite 60 solicitudes por minuto por IP. Cuando se supera el límite, el middleware devuelve 429 y nosotros añadimos Retry-After.


using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddRateLimiter(options =>
{
    // Código de estado por defecto para solicitudes rechazadas
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // Nombre de la policy: "ip"
    options.AddPolicy("ip", httpContext =>
    {
        var ip = GetClientIp(httpContext);

        // Partición por IP: cada IP obtiene su propio limiter
        return RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: ip,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 60,                 // 60 solicitudes
                Window = TimeSpan.FromMinutes(1), // por 1 minuto
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 0                    // sin cola: rechazar inmediatamente
            });
    });

    // Añadir Retry-After cuando se rechaza
    options.OnRejected = async (context, ct) =>
    {
        context.HttpContext.Response.Headers["Retry-After"] = "60";
        await context.HttpContext.Response.WriteAsync("Demasiadas solicitudes.", ct);
    };
});

var app = builder.Build();

app.UseRateLimiter();

app.MapControllers()
   .RequireRateLimiting("ip");

app.Run();

static string GetClientIp(HttpContext httpContext)
{
    // Si estás detrás de un reverse proxy, revisa la nota más abajo.
    return httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}

¿Dónde aplicar la policy?

En el ejemplo anterior aplicamos rate limiting a todos los controllers con MapControllers().RequireRateLimiting("ip"). Si quieres aplicarlo solo en endpoints específicos, puedes configurarlo a nivel de endpoint.


Prueba rápida

Puedes simular un pico de solicitudes (Linux/macOS) así:


for i in {1..80}; do
  curl -s -o /dev/null -w "%{http_code}\n" https://localhost:5001/api/customers
done

Después de la solicitud 60 (dentro del mismo minuto), deberías ver respuestas 429.


Nota importante: reverse proxy y la IP real del cliente

Si tu API está detrás de un reverse proxy (NGINX, Cloudflare, Azure App Gateway, etc.), RemoteIpAddress puede contener la IP del proxy en lugar de la IP real del cliente. En ese caso, el rate limiting por IP no funcionará correctamente a menos que configures los forwarded headers.

En ASP.NET Core puedes habilitar forwarded headers así (en producción, confía solo en proxies conocidos):


using Microsoft.AspNetCore.HttpOverrides;

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

    // IMPORTANTE: en producción, añade IPs / redes de proxies conocidos
    // options.KnownProxies.Add(IPAddress.Parse("10.0.0.10"));
});

var app = builder.Build();
app.UseForwardedHeaders();

Cuando los forwarded headers están correctamente configurados, RemoteIpAddress reflejará la IP real del cliente.


Mejoras comunes


TL;DR

  • El rate limiting por IP es una forma simple de proteger APIs contra picos y abuso.
  • .NET 8 ofrece rate limiting integrado con AddRateLimiter() + UseRateLimiter().
  • Cuando se excede el límite, devuelve 429 y añade Retry-After.
  • Detrás de un proxy, configura forwarded headers para obtener la IP real del cliente.