AspNetCoreRateLimit is an ASP.NET Core rate limiting solution designed to control the rate of requests that clients can make to a Web API or MVC app based on IP address or client ID. The AspNetCoreRateLimit package contains an IpRateLimitMiddleware and a ClientRateLimitMiddleware, with each middleware you can set multiple limits for different scenarios like allowing an IP or Client to make a maximum number of calls in a time interval like per second, 15 minutes, etc. You can define these limits to address all requests made to an API or you can scope the limits to each API URL or HTTP verb and path.
publicvoidConfigureServices(IServiceCollection services) { // needed to load configuration from appsettings.json services.AddOptions();
// needed to store rate limit counters and ip rules services.AddMemoryCache();
//load general configuration from appsettings.json // IP services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting")); // Client services.Configure<ClientRateLimitOptions>(Configuration.GetSection("ClientRateLimiting"));
// IP //load ip rules from appsettings.json services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies")); // Client //load client rules from appsettings.json services.Configure<ClientRateLimitPolicies>(Configuration.GetSection("ClientRateLimitPolicies"));
// inject counter and rules stores services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>(); services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
services.AddControllers();
// https://github.com/aspnet/Hosting/issues/793 // the IHttpContextAccessor service is not registered by default. // the clientId/clientIp resolvers use it. services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
If you load-balance your app, you’ll need to use IDistributedCache with Redis or SQLServer so that all kestrel instances will have the same rate limit store. Instead of the in-memory stores you should inject the distributed stores like this:
1 2 3 4 5 6 7 8
// inject counter and rules distributed cache stores
// IP services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>(); // Client services.AddSingleton<IClientPolicyStore, DistributedCacheClientPolicyStore>();
If it is set to false then the limits will apply globally and only rules that have as endpoint * will apply. For example if you set a limit of 5 calls per second, any HTTP call to any endpoint will count towards that limit.
If it is set to true then the limits will apply for each endpoint as in {HTTP_Verb}:{PATH}. For example if you set a limit of 5 calls per second for *:/api/values a client can call get:/api/values 5 times per second but also 5 times put:/api/values.
Endpoint
*: A placeholder to anything
{HTTP_Verb}:{PATH}: Endpoint format like get:/api/license
Period
Period format is {INT}{PERIOD_TYPE}, you can use one of the following period types: s, m, h, d.
s: second
m: minute
h: hour
d: day
HttpStatusCode
is set to 429, which means the HTTP status code returned to the client after the limit is triggered.
ClientIdHeader
It is used to extract the client id, if a client id is present in this header and matches a value specified in ClientWhitelist then no rate limits are applied.
Limit
Number of requests allowed. Limit format is {LONG}.
At application startup the IP/client rate limit rules defined in appsettings.json are loaded in cache by either MemoryCacheClientPolicyStore or DistributedCacheClientPolicyStore depending on what type of cache provider you are using. You can access the IP/client policy store inside a controller and modify the rules like so:
privatestatic MemoryCache Cache { get; } = new MemoryCache(new MemoryCacheOptions());
publicoverridevoidOnActionExecuting(ActionExecutingContext context) { var ipAddress = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress; var memoryCacheKey = $"{Name}-{ipAddress}";
if (!Cache.TryGetValue(memoryCacheKey, outbool entry)) { var cacheEntryOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromSeconds(Seconds)); Cache.Set(memoryCacheKey, true, cacheEntryOptions); } else { context.Result = new ContentResult { Content = $"Requests are limited to 1, every {Seconds} seconds.", }; context.HttpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests; } } } }
Name: A name for uniqueness.
Seconds: An integer to store the number of seconds.
We need to override the virtual OnActionExecuting method from our inherited class. Within this method we are doing the following:
Obtaining the users ip address.
Storing the ip address in our memory cache, with a timeout based on the number of seconds we have assigned to our rate limiting action filter attribute.
Returning an error message and a relevant status code (HTTP 429), in the event that the user hits our rate limit for the Api.
Now to apply our action filter attribute to our desired controller action. I’ve added a simple Api endpoint for this example, and applied the attribute to the method, stating that we want to rate limit to 1 request, every 5 seconds.