Without rate limiting, a single misbehaving client can overwhelm your API, causing slowdowns or outages for everyone. Rate limiting protects your infrastructure, ensures fair usage, and prevents abuse.
ASP.NET Core 7+ includes powerful built-in rate limiting middleware - no third-party packages required.
// Program.csvar builder = WebApplication.CreateBuilder(args);builder.Services.AddRateLimiter(options =>{// Return proper 429 status with Retry-After headeroptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;// Global rate limit - 100 requests per minute per IP.// RemoteIpAddress is only the real client if your forwarded-headers// middleware is correctly configured for your proxy chain - see// "Do you know the client IP address can be spoofed?" below.options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>RateLimitPartition.GetFixedWindowLimiter(partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "anonymous",factory: _ => new FixedWindowRateLimiterOptions{PermitLimit = 100,Window = TimeSpan.FromMinutes(1)}));});var app = builder.Build();app.UseRateLimiter();
😐 Figure: OK example - Configure global rate limiting by IP address, but only once your app resolves a trustworthy client IP
RemoteIpAddress is only the real client behind a correctly configured proxy chain. Behind a CDN or load balancer, with the default configuration, it is either the proxy's own IP - collapsing every user into one shared bucket - or a value the caller supplied and can change per request. Both defeat the limiter. See Do you know the client IP address can be spoofed? to configure trusted proxies correctly before partitioning on IP.
Simple counter that resets at fixed intervals. Can allow bursts at window boundaries.
options.AddFixedWindowLimiter("fixed", config =>{config.PermitLimit = 10;config.Window = TimeSpan.FromSeconds(10);config.QueueLimit = 2; // Allow 2 requests to queue});
Smoother rate limiting that prevents boundary bursts by using segments within the window.
options.AddSlidingWindowLimiter("sliding", config =>{config.PermitLimit = 10;config.Window = TimeSpan.FromSeconds(10);config.SegmentsPerWindow = 5; // 5 segments of 2 seconds each});
Allows controlled bursts while maintaining average rate. Tokens refill over time.
options.AddTokenBucketLimiter("token", config =>{config.TokenLimit = 100; // Bucket size (max burst)config.TokensPerPeriod = 10; // Refill rateconfig.ReplenishmentPeriod = TimeSpan.FromSeconds(1);});
✅ Figure: Good example - Choose the algorithm that fits your use case
// Public endpoints - stricter limitsapp.MapGet("/api/public/search", SearchHandler).RequireRateLimiting("fixed");// Authenticated endpoints - higher limitsapp.MapGet("/api/products", GetProductsHandler).RequireRateLimiting("authenticated");// Disable rate limiting for health checksapp.MapGet("/health", () => Results.Ok()).DisableRateLimiting();
✅ Figure: Good example - Apply different rate limits based on endpoint sensitivity
builder.Services.AddRateLimiter(options =>{options.AddPolicy("per-user", context =>{var userId = context.User?.FindFirstValue(ClaimTypes.NameIdentifier);return RateLimitPartition.GetTokenBucketLimiter(partitionKey: userId ?? "anonymous",factory: _ => new TokenBucketRateLimiterOptions{TokenLimit = userId != null ? 1000 : 100, // Higher limit for authenticated usersTokensPerPeriod = userId != null ? 100 : 10,ReplenishmentPeriod = TimeSpan.FromMinutes(1)});});});
✅ Figure: Good example - Give authenticated users higher rate limits
options.OnRejected = async (context, cancellationToken) =>{context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)){context.HttpContext.Response.Headers.RetryAfter = retryAfter.TotalSeconds.ToString();}await context.HttpContext.Response.WriteAsJsonAsync(new{error = "Too many requests",retryAfter = retryAfter.TotalSeconds}, cancellationToken);};
✅ Figure: Good example - Return Retry-After header so clients know when to retry
| Algorithm | Best For |
| Fixed Window | Simple rate limiting, acceptable burst at boundaries |
| Sliding Window | Smoother limiting, preventing boundary exploitation |
| Token Bucket | APIs that allow controlled bursts (e.g., batch operations) |
| Concurrency | Limiting simultaneous requests (e.g., expensive operations) |