Implementing Rate Limiting
Protect endpoints from abuse with rate limiting strategies.
By EMEPublished: February 20, 2025
rate limitingapi protectionmiddlewarethrottling
A Simple Analogy
Rate limiting is like a speed limit on highways. It prevents abuse by restricting how fast (many requests) can happen.
Why Rate Limiting?
- Abuse prevention: Block malicious clients
- Resource protection: Don't overload servers
- Fair access: Prevent monopolization
- Cost control: Limit API usage costs
- DoS protection: Mitigate attacks
Per-IP Rate Limiting
public class IpRateLimitMiddleware
{
private readonly RequestDelegate _next;
private readonly ConcurrentDictionary<string, RateLimit> _limits;
public IpRateLimitMiddleware(RequestDelegate next)
{
_next = next;
_limits = new ConcurrentDictionary<string, RateLimit>();
}
public async Task InvokeAsync(HttpContext context)
{
var ip = context.Connection.RemoteIpAddress.ToString();
var limit = _limits.GetOrAdd(ip, _ => new RateLimit(100, TimeSpan.FromMinutes(1)));
if (!limit.IsAllowed())
{
context.Response.StatusCode = 429;
context.Response.Headers.Add("Retry-After", "60");
await context.Response.WriteAsync("Rate limit exceeded");
return;
}
await _next(context);
}
public class RateLimit
{
private int _remaining;
private DateTime _resetTime;
public RateLimit(int limit, TimeSpan window)
{
_remaining = limit;
_resetTime = DateTime.UtcNow.Add(window);
}
public bool IsAllowed()
{
if (DateTime.UtcNow >= _resetTime)
{
_remaining = 100;
_resetTime = DateTime.UtcNow.AddMinutes(1);
}
if (_remaining > 0)
{
_remaining--;
return true;
}
return false;
}
}
}
Header-Based Response
context.Response.Headers.Add("X-RateLimit-Limit", "100");
context.Response.Headers.Add("X-RateLimit-Remaining", "45");
context.Response.Headers.Add("X-RateLimit-Reset", "1645000000");
// Or
context.Response.Headers.Add("Retry-After", "60"); // seconds to wait
Sliding Window
public class SlidingWindowCounter
{
private readonly Queue<DateTime> _requests = new();
private readonly int _limit;
private readonly TimeSpan _window;
public SlidingWindowCounter(int limit, TimeSpan window)
{
_limit = limit;
_window = window;
}
public bool IsAllowed()
{
var now = DateTime.UtcNow;
var cutoff = now.Subtract(_window);
// Remove old requests
while (_requests.Count > 0 && _requests.Peek() < cutoff)
{
_requests.Dequeue();
}
if (_requests.Count < _limit)
{
_requests.Enqueue(now);
return true;
}
return false;
}
}
Endpoint-Specific Limits
[RateLimit(limit: 100, window: 60)] // 100 requests per 60 seconds
[HttpGet("products")]
public async Task<IActionResult> GetProducts()
{
return Ok(await _service.GetProductsAsync());
}
[RateLimit(limit: 10, window: 60)] // Stricter for writes
[HttpPost("products")]
public async Task<IActionResult> CreateProduct(CreateProductRequest req)
{
var product = await _service.CreateAsync(req);
return CreatedAtAction(nameof(GetProducts), new { id = product.Id });
}
Best Practices
- Be generous: Don't limit legitimate users
- Inform clients: Use headers clearly
- Transparent: Document limits in API docs
- Graceful degradation: Queue instead of reject
- Monitor: Track limit violations
Related Concepts
- Quota management
- Throttling
- Traffic shaping
- DDoS protection
Summary
Implement rate limiting to protect endpoints from abuse. Use per-IP tracking, sliding windows, and clear response headers.