The title is pretty self explanatory. How do I rate limit the following limiter by IP address? In other words, each IP address is able to do 2 requests per 10 seconds.
app.UseRateLimiter(new RateLimiterOptions
{
OnRejected = (context, _) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
app.Logger.LogWarning("Rate limit exceeded, retry after {RetryAfter} seconds", retryAfter.TotalSeconds);
}
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
return new ValueTask();
}
}
// You're allowed 2 requests per 10 seconds.
.AddFixedWindowLimiter("fixed",
new FixedWindowRateLimiterOptions(2,
window: TimeSpan.FromSeconds(10),
queueProcessingOrder: QueueProcessingOrder.OldestFirst,
queueLimit: 0,
autoReplenishment: true)));
app.MapControllers().RequireRateLimiting("fixed");
Using a PartitionedRateLimiter
is the way to go here:
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
httpContext.Request.Headers.TryGetValue("X-Forwarded-For", out StringValues forwardedFor);
string? ipAddress = forwardedFor.FirstOrDefault() ?? httpContext.Request.HttpContext.Connection.RemoteIpAddress?.ToString();
if (ipAddress == null) return RateLimitPartition.GetNoLimiter("none");
return RateLimitPartition.GetTokenBucketLimiter(ipAddress, (key) =>
{
return new TokenBucketRateLimiterOptions
{
TokenLimit = 100,
ReplenishmentPeriod = TimeSpan.FromMinutes(15),
TokensPerPeriod = 25
};
});
});
The tricky part is getting the IP address. As noted in the comments, when behind a reverse proxy or API gateway, double check the IP is forwarded in the correct header (in this example it is X-Forwarded-For
). Else you will probably rate-limit your reverse proxy, which is not what you want. I use nginx
as a reverse proxy, and the following configuration will always populate the X-Forwarded-For
header to your app:
# inside nginx reverse proxy config file
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
That being said, there are some issues with IP rate limiting. Some home-network providers will use NAT even with IPv4, as do some cellular network providers - so potential thousands of users using the same IPv4. Additionally, when you use IPv6, you might want to modify the code to use the whole /64 or even /48 subnet of the source host as a partion key.