Chargement...

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

Limitez les requêtes par IP avec le middleware de rate limiting .NET 8 et renvoyez 429 avec Retry-After pour protéger votre API.

Pour une API publique, l’un des problèmes les plus fréquents est la rafale de requêtes (request spike). Un bot, un client défectueux ou une logique de retry trop agressive peut générer très rapidement un grand nombre d’appels. Une des premières protections consiste à mettre en place du rate limiting : on limite le nombre de requêtes autorisées sur une fenêtre de temps.

Avec .NET 8, vous pouvez le faire de manière moderne, sans middleware “maison”, en utilisant le middleware de rate limiting intégré. Dans cet exemple, on applique une limite par IP et, en cas de dépassement, on renvoie 429 Too Many Requests. On ajoute également le header Retry-After afin d’indiquer au client quand réessayer.


Objectif


Program.cs (Fixed Window Rate Limiting par IP)

Cette configuration autorise 60 requêtes par minute et par IP. En cas de dépassement, le middleware renvoie 429 et nous ajoutons Retry-After.


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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddRateLimiter(options =>
{
    // Code HTTP par défaut pour les requêtes rejetées
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

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

        // Partition par IP : chaque IP a son propre limiter
        return RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: ip,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 60,                 // 60 requêtes
                Window = TimeSpan.FromMinutes(1), // par 1 minute
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 0                    // pas de file : rejet immédiat
            });
    });

    // Ajouter Retry-After en cas de rejet
    options.OnRejected = async (context, ct) =>
    {
        context.HttpContext.Response.Headers["Retry-After"] = "60";
        await context.HttpContext.Response.WriteAsync("Trop de requêtes.", ct);
    };
});

var app = builder.Build();

app.UseRateLimiter();

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

app.Run();

static string GetClientIp(HttpContext httpContext)
{
    // Si vous êtes derrière un reverse proxy, voir la note plus bas.
    return httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}

Où appliquer la policy ?

Dans l’exemple ci-dessus, on applique le rate limiting à tous les controllers via MapControllers().RequireRateLimiting("ip"). Si vous souhaitez le limiter à certains endpoints uniquement, vous pouvez l’appliquer au niveau endpoint.


Test rapide

Vous pouvez simuler une rafale de requêtes (Linux/macOS) :


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

Après la 60e requête (dans la même minute), vous devriez voir des réponses 429.


Note importante : reverse proxy et IP réelle du client

Si votre API est derrière un reverse proxy (NGINX, Cloudflare, Azure App Gateway, etc.), RemoteIpAddress peut contenir l’IP du proxy au lieu de l’IP réelle du client. Dans ce cas, le rate limiting par IP ne fonctionnera pas correctement tant que vous n’aurez pas configuré les forwarded headers.

Dans ASP.NET Core, vous pouvez activer les forwarded headers comme ceci (en production, ne faites confiance qu’aux proxies connus) :


using Microsoft.AspNetCore.HttpOverrides;

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

    // IMPORTANT : en production, ajoutez les IP / réseaux de proxies connus
    // options.KnownProxies.Add(IPAddress.Parse("10.0.0.10"));
});

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

Une fois les forwarded headers correctement configurés, RemoteIpAddress reflètera l’IP réelle du client.


Améliorations courantes


TL;DR

  • Le rate limiting par IP est une protection simple contre les rafales et les abus.
  • .NET 8 fournit un rate limiting intégré via AddRateLimiter() + UseRateLimiter().
  • En cas de dépassement, renvoyez 429 et ajoutez Retry-After.
  • Derrière un proxy, configurez les forwarded headers pour obtenir l’IP réelle du client.