How to Protect C# APIs with Rate Limiting Middleware
Rate limiting middleware protects web APIs by controlling the rate of incoming traffic, stopping malicious bots, brute-force attempts, and denial-of-service (DoS) attacks before they overwhelm server resources. This article explores how rate limiting works, the primary algorithms used to control request flow, and how to implement native rate limiting middleware in modern C# and ASP.NET Core applications to safeguard your endpoints.
What Is Rate Limiting Middleware?
Rate limiting middleware is a pipeline component that intercepts incoming HTTP requests and monitors how frequently a client interacts with an application. It evaluates each request against predefined criteria—such as client IP address, authenticated user ID, or API key.
If the client's request count exceeds the configured threshold within
a specific timeframe, the middleware halts request processing and
immediately returns an HTTP 429 Too Many Requests response.
This prevents downstream controllers, business logic, and databases from
absorbing excessive compute overhead.
Common Rate Limiting Algorithms
ASP.NET Core supports several standard rate-limiting algorithms
through the System.Threading.RateLimiting namespace:
- Fixed Window: Tracks requests across static time intervals (e.g., 100 requests every 60 seconds). It resets the counter at the start of each new window. While simple, it can permit request bursts near window boundaries.
- Sliding Window: Divides time windows into smaller segments to smooth out boundary bursts, creating a moving calculation of permitted requests over time.
- Token Bucket: Accumulates tokens at a sustained rate up to a maximum capacity. Each request consumes a token. This permits controlled bursts of traffic as long as unused tokens remain in the bucket.
- Concurrency Limiter: Restricts the maximum number of simultaneous requests being processed at any single moment, preventing connection starvation.
Implementing Rate Limiting in C# (ASP.NET Core)
Starting with .NET 7 and .NET 8, ASP.NET Core provides built-in rate
limiting middleware via the
Microsoft.AspNetCore.RateLimiting package.
Here is how to configure and apply a fixed-window policy in
Program.cs:
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// 1. Configure the rate limiting services
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Define a fixed window policy
options.AddFixedWindowLimiter("FixedPolicy", opt =>
{
opt.PermitLimit = 10;
opt.Window = TimeSpan.FromSeconds(10);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2; // Queue up to 2 extra requests before rejecting
});
});
builder.Services.AddControllers();
var app = builder.Build();
// 2. Add the middleware to the pipeline
app.UseRateLimiter();
app.UseAuthorization();
app.MapControllers();
// Apply the policy to a minimal API endpoint
app.MapGet("/api/secure-data", () => Results.Ok(new { Data = "Protected resource" }))
.RequireRateLimiting("FixedPolicy");
app.Run();To protect a standard controller, apply the
[EnableRateLimiting] attribute directly:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("FixedPolicy")]
public class SensitiveDataController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("Secured by FixedPolicy");
}
}Rate Limiting by Client Identity
Applying a global limit risks penalizing all users when a single bad actor sends malicious traffic. A better approach partitions limits by client identity, such as IP address or authentication token:
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("PartitionByIp", httpContext =>
{
// Extract client IP address as the partition key
var clientIp = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
return RateLimitPartition.GetTokenBucketLimiter(clientIp, _ => new TokenBucketRateLimiterOptions
{
TokenLimit = 50,
QueueLimit = 0,
ReplenishmentPeriod = TimeSpan.FromSeconds(1),
TokensPerPeriod = 5,
AutoReplenishment = true
});
});
});Best Practices for Protecting APIs
- Inform the Client: Include standard headers such as
Retry-Afterin HTTP 429 responses so legitimate clients know when to safely retry. - Combine with Authentication: Enforce stricter rate limits on unauthenticated endpoints (e.g., login, password reset) and grant higher thresholds to authenticated or paid tiers.
- Use Distributed Stores for Multi-Instance Deployments: Built-in middleware tracks counters in-memory. If running multiple API replicas behind a load balancer, use a distributed cache such as Redis to synchronize request counters across nodes or offload rate limiting to an API Gateway (e.g., YARP, Azure API Management, or Cloudflare).