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
- Limiter les requêtes par adresse IP.
- Renvoyer
429 Too Many Requestslorsque la limite est dépassée. - Ajouter
Retry-Afterpour aider les clients à gérer un backoff proprement. - Garder une configuration simple et réutilisable sur les endpoints.
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
- Appliquer des limites différentes selon les endpoints (par ex. une limite plus stricte pour le login).
- Whitelister des IP internes ou exclure les endpoints de health check.
- Si vous avez plusieurs instances, utiliser un store distribué (les limites en mémoire sont par instance).
- Renvoyer une erreur structurée (ex. ProblemDetails) au lieu d’un plain text.
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
429et ajoutezRetry-After. - Derrière un proxy, configurez les forwarded headers pour obtenir l’IP réelle du client.