Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AspNetCoreRateLimit Does not use query parameters when matching rules

I'm using AspNetCoreRateLimit library with Asp.Net Core 2.2 web api. I've taken IpRateLimiting into use with it's default settings in Startup.cs as seen AspNetCoreRateLimit wiki.

I have API endpoint with query parameters, and it is used with http GET queries as following (see parameters startDate and stopDate):

GET "https://host/api/endpoint/path?startDate=2020-04-04&stopDate=2020-04-04"

I want to limit only unique requests (with unique parameter combinations) to 5 requests per hour. So, for example, the following scenario should be possible in 1 hour:

5 times: GET "https://host/api/endpoint/path?startDate=2020-04-04&stopDate=2020-04-04"
5 times: GET "https://host/api/endpoint/path?startDate=2020-04-05&stopDate=2020-04-05"

The problem is that I can send only total 5 requests per hour regardless of parameters.

Following is my IpRateLimiting Settings from appsettings.json.

"IpRateLimiting": {
    "EnableEndpointRateLimiting": true,
    "StackBlockedRequests": false,
    "RealIPHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    "GeneralRules": [
      {
        "Endpoint": "*:/api/endpoint/path",
        "Period": "1h",
        "Limit": 5
      }
    ]
  }

Please note, that I don't want to change the endpoint route as proposed in this good answer by @Yongqing Yu, because there are a bunch of API clients out there using my API and I do not want to introduce any breaking changes.

like image 826
Risto M Avatar asked Mar 03 '23 11:03

Risto M


2 Answers

You can change the route of the corresponding action and turn the parameter directly into a part of the path like 'https://host/api/endpoint/path/2020-04-04/2020-04-04', so that the Endpoint in GeneralRules can satisfy your condition by *.

You can refer to this.

Here is my demo:

[Route("api/[controller]")]
[ApiController]
public class DefaultController : ControllerBase
{
    [HttpGet("Test/{startDate}/{stopDate}")]
    public string Test(string startDate, string stopDate)
    {
        return "Ok";
    }
}

appsettings.json:

"IpRateLimiting": {
        "EnableEndpointRateLimiting": true,
        "StackBlockedRequests": false,
        "RealIPHeader": "X-Real-IP",
        "ClientIdHeader": "X-ClientId",
        "HttpStatusCode": 429,
        "GeneralRules": [
          {
            "Endpoint": "*:/api/default/Test/*",
            "Period": "1h",
            "Limit": 5
          }
        ]
      }

Here is the test result:

enter image description here

like image 174
LouraQ Avatar answered May 06 '23 03:05

LouraQ


I found a solution and thus answering to myself. In my case I am not able to change the controller method route as proposed in another answer.

As mentioned here it is possible to implement own path extraction logic. I wrote custom IpRateLimitMiddleware and overrided ResolveIdentity-method as follows:

public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware
{
    private readonly ILogger<CustomIpRateLimitMiddleware> _logger;
    private readonly IRateLimitConfiguration _config;

    public CustomIpRateLimitMiddleware(RequestDelegate next,
        IOptions<IpRateLimitOptions> options,
        IRateLimitCounterStore counterStore,
        IIpPolicyStore policyStore,
        IRateLimitConfiguration config,
        ILogger<CustomIpRateLimitMiddleware> logger)
    : base(next, options, counterStore, policyStore, config, logger)

    {
        _config = config;
        _logger = logger;
    }

    public override ClientRequestIdentity ResolveIdentity(HttpContext httpContext)
    {
        var identity = base.ResolveIdentity(httpContext);

        if (httpContext.Request.Query == null && !httpContext.Request.Query.Any())
        {
            return identity;
        }

        StringBuilder path = new StringBuilder(httpContext.Request.Path.ToString().ToLowerInvariant());
        foreach (var parameter in httpContext.Request.Query)
        {
            path.Append("/" + parameter.Value);
        }
        identity.Path = path.ToString();
        return identity;
    }
}

And this is initialized in Startup.cs as following:

ConfigureServices-method:

services.AddOptions();
services.AddMemoryCache();
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

Configure-method:

app.UseMiddleware<CustomIpRateLimitMiddleware>();

The code snippet above amends query for the middleware so it looks like those query parameters are part of the path.

So instead of this:

/api/endpoint/path?startDate=2020-04-04&stopDate=2020-04-04"

AspNetCoreRateLimit is getting path as following format:

/api/endpoint/path/2020-04-04/2020-04-04

..and now my rate limiting configuration can be this:

"IpRateLimiting": {
    "EnableEndpointRateLimiting": true,
    "StackBlockedRequests": false,
    "RealIPHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    "GeneralRules": [
      {
        "Endpoint": "*:/api/path/*",
        "Period": "1h",
        "Limit": 5
      }
    ]
  }
like image 26
Risto M Avatar answered May 06 '23 04:05

Risto M