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 IPoptions.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: Good example - Configure global rate limiting by IP address
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) |