Search code examples
c#.net-corerate-limiting

Implementing rate limiting for public endpoints in C# and .NET Core to mitigate brute force attacks


I need a way in C# / .NET Core app to limit the number of queries that can be made to a public endpoint within a certain amount of time. This is to prevent a brute force attack.

For example, a user makes a request to endpoint xx and it should allow only 3 requests, rejecting the fourth. How can I achieve this if it's a public endpoint where I cannot identify who is making the request?


Solution

  • You can achieve the rate limiting via the nuget AspNetCoreRateLimit by implementing a middleware service.

    First you need to add the package into your project via :

    dotnet add package AspNetCoreRateLimit
    

    or use install this package via manage nuget packages UI interface in Visual Studio.

    Secondly, update your appsettings.json file to use configuration like this:

    {
      "IpRateLimiting": {
        "EnableEndpointRateLimiting": true,
        "StackBlockedRequests": false,
        "RealIpHeader": "X-Real-IP",
        "ClientIdHeader": "X-ClientId",
        "HttpStatusCode": 429,
        "GeneralRules": [
          {
            "Endpoint": "*:/api/publicendpoint",
            "Period": "1m",
            "Limit": 3
          }
        ]
      },
      "IpRateLimitPolicies": {
        "IpRules": []
      }
    }
    

    Thirdly, register rate limiting services in your Startup.cs or Program.cs like this:

    public class Startup
    {
        public IConfiguration Configuration { get; }
    
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting")); // Load configurations added from appsettings.json
            services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));              
            services.AddMemoryCache();   // Specify memory cache to store count limits            
            services.AddInMemoryRateLimiting();  // Add rate limiting services  
            services.AddMvc();          
            services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>(); // Specify IpRateLimit middleware as single instance
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            app.UseRouting();    
            app.UseAuthorization();            
    
            app.UseIpRateLimiting(); // Enable IpRateLimiting middleware which we will implement in fourth stage    
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
    

    Fourthly, implement the middleware to limit the number of request like this:

    public class RateLimitingMiddleware
    {
        private readonly RequestDelegate _next;
        private static readonly MemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());
        private static readonly TimeSpan _timeWindow = TimeSpan.FromMinutes(1);
        private static readonly int _maxRequests = 3;
    
        public RateLimitingMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            var ipAddress = context.Connection.RemoteIpAddress.ToString();
    
            if (IsRateLimited(ipAddress))
            {
                context.Response.StatusCode = 429;
                await context.Response.WriteAsync("Too many requests. Please try again later.");
                return;
            }
    
            await _next(context);
        }
    
        private bool IsRateLimited(string ipAddress)
        {
            var cacheKey = $"RateLimit_{ipAddress}";
    
            if (_memoryCache.TryGetValue(cacheKey, out int requestCount))
            {
                if (requestCount >= _maxRequests)
                {
                    return true;
                }
    
                _memoryCache.Set(cacheKey, ++requestCount, _timeWindow);
            }
            else
            {
                _memoryCache.Set(cacheKey, 1, _timeWindow);
            }
    
            return false;
        }
    }
    
    public static class RateLimitingMiddlewareExtensions
    {
        public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RateLimitingMiddleware>();
        }
    }
    

    Now you can specify the and tell your application to use in your Startup.cs or Program.cs as shown here:

    app.UseRateLimiting();