ASP.NET Core cookbook · Intermediate
Rate limit per user and per IP
A single client can exhaust the API, and login endpoints are brute-forced.
Approach
Use partitioned rate limiters: per user (or API key) for authenticated traffic, per IP for anonymous traffic,
and a strict policy for login. Return 429 with Retry-After.
Solution
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.OnRejected = (ctx, _) =>
{
if (ctx.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
ctx.HttpContext.Response.Headers.RetryAfter = ((int)retryAfter.TotalSeconds).ToString();
return ValueTask.CompletedTask;
};
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(http =>
{
var user = http.User.Identity?.IsAuthenticated == true ? http.User.Identity.Name : null;
return user is not null
? RateLimitPartition.GetTokenBucketLimiter($"user:{user}", _ => new TokenBucketRateLimiterOptions
{ TokenLimit = 100, TokensPerPeriod = 50, ReplenishmentPeriod = TimeSpan.FromSeconds(10) })
: RateLimitPartition.GetFixedWindowLimiter($"ip:{http.Connection.RemoteIpAddress}", _ => new FixedWindowRateLimiterOptions
{ PermitLimit = 60, Window = TimeSpan.FromMinutes(1) });
});
options.AddPolicy("login", http => RateLimitPartition.GetSlidingWindowLimiter(
$"login:{http.Connection.RemoteIpAddress}", _ => new SlidingWindowRateLimiterOptions
{ PermitLimit = 5, Window = TimeSpan.FromMinutes(1), SegmentsPerWindow = 6 }));
});
var app = builder.Build();
app.UseRateLimiter();
app.MapPost("/login", () => Results.Ok()).RequireRateLimiting("login");
app.Run();
Pitfalls
- Using
RemoteIpAddressbehind a proxy withoutUseForwardedHeaders(everyone shares the proxy IP). - In-process limits with several instances; each instance allows the full rate.
- Unbounded partition keys from user input (headers) that grow memory.
Variations
- Apply global limits at the API gateway and keep per-endpoint limits in the app.
[EnableRateLimiting("policy")]and[DisableRateLimiting]work on controllers too.
Further reading: Rate limiting middleware
Reference: https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit
More ASP.NET Core recipes
- Accept file uploads with size and type limits
- Add OpenTelemetry traces and metrics
- Apply EF Core migrations safely during deployment
- Bind and validate options at startup
- Cache responses with output caching and evict by tag
- Call external APIs with typed HttpClients and resilience
- Configure CORS for a browser front end
- Expose liveness and readiness health checks