# Using Rate Limiting in Asp.Net Core Minimal APIs

In this blog post will learn about Rate Limiting and how we can implement it in Asp.net Core Minimal API.

# Rate Limiting

## What is it?

Rate limiting is the process of limiting how much a resource can be accessed. In the APIs environment, we can define a rate limiter that allows 100 requests every minute and rejects any more requests until restarting the counter again.

## How does it work?

Rate limiting is based on tracking the client IP where the requests are sent from and monitoring how much time elapses between each request.

The rate-limiting middleware measures the time between each request from the client IP and the number of requests within a specified rule. If the application gets too many requests from a single client IP in the defined period, the rate-limiting middleware will only allow the request from the client once the reset point is reached. Usually, this will be displayed in the response header to the client.

## What are the benefits of using Rate Limiting?

Using rate limiting can motivate third-party developers to pay more to use our API(s).

Another benefit of implementing rate limiting is to protect our API against malicious bot attacks.

# Implementation

First, it is necessary to install the nugget package [AspNetCoreRateLimit](https://www.nuget.org/packages/AspNetCoreRateLimit). After we need to do some configurations:

```csharp
// needed to store rate limit counters and ip rules
builder.Services.AddMemoryCache();

//load general configuration from appsettings.json
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));

//load ip rules from appsettings.json
builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));

// inject counter and rules stores    
builder.Services.AddInMemoryRateLimiting();

// configuration (resolvers, counter key builders)
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
```

To use the middleware, we should use the following code. We need to register the middleware before any other components.

```csharp
app.UseIpRateLimiting();
```

We can add the rules in the `appsettings.json` file. The following code shows a basic sample of rules.

```json
  "IpRateLimitingSettings": {
    "EnableEndpointRateLimiting": true,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "10s",
        "Limit": 5
      }
    ]
  }
```

Let's understand the rules that were defined in the `appsettings` file.

`EnableEndpointRateLimiting` is set to `false,` and then the limits will apply globally, and only the rules that have as endpoint `*` will apply. For example, if we put a limit of five calls per ten seconds, any HTTP call to any endpoint will count towards that limit. If set to `true`, the client can call `{HTTP_METHOD}{PATH}` five times per ten seconds for each HTTP method (**GET, POST, PUT, DELETE**).

If `StackBlockedRequests` is set to `false`, rejected calls are not added to the throttle counter. If a client makes three requests per second and we've selected a limit of one call per second, other limits like per minute or per day counters will only record the first call, which wasn't blocked. If we want rejected requests to count toward the other limits, we must set `StackBlockedRequests` to `true`.

The `RealIpHeader` extracts the client IP when your Kestrel server is behind a reverse proxy. If the proxy uses a different header, then `X-Real-IP` uses this option to set it up.

The `ClientIdHeader` is used to extract the client id for whitelisting. If a client id is present in this header and matches a value specified in ClientWhitelist, then no rate limits are applied.

The rate limit values are displayed in the headers when an endpoint is called.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675004248740/054c47e1-5c38-4f53-9057-9904e411a65f.png align="center")

The following image shows the results when the client exceeds the limit of calls.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675002545452/e2053e9e-9592-43da-8fa5-d150e1651411.png align="center")

## Rate Limit General Rules

We can have multiple rules in our application, changing the endpoint, period and limit. We've defined in our settings for all the endpoints a limit of five calls in a period of ten seconds. The following code shows two rules for all the endpoints but with different limits and periods. The behaviour will depend on the property `StackBlockedRequests` if rejected calls are added or not to the throttle counter.

```json
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "10s",
        "Limit": 5
      },
      {
        "Endpoint": "*",
        "Period": "10m",
        "Limit": 100
      }
    ]
```

We also can define different settings per endpoint.

```json
"GeneralRules": [
      {
        "Endpoint": "get:/muscles",
        "Period": "5s",
        "Limit": 3
      },
      {
        "Endpoint": "get:/muscles/*",
        "Period": "1m",
        "Limit": 10
      },
      {
        "Endpoint": "post:/muscles",
        "Period": "1m",
        "Limit": 3
      }
    ]
```

The `EndpointWhitelist` property is used to define the endpoints with no limitations.

```json
"EndpointWhitelist": [ "get:/license", "*:/status" ],
```

We can use the `ClientWhitelist` property to define the list of `X-ClientId` values with no limitations in calling the endpoints. For this, the `X-ClientId` should be present in the headers.

```json
"ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675006747283/29e8e277-0854-4bdd-88f7-8bb74bbc096a.png align="center")

### Overriding General Rules

The general rules can be overridden using the `IpRateLimitPolicies` settings. The IP field supports IP v4 and v6 values and ranges like `192.168.0.0/24`, `fe80::/10` or `192.168.0.0-192.168.0.255`.

To load the `IpRateLimitPolicies,` the following code should be registered.

```json
builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));

builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
```

```json
"IpRateLimitPolicies": {
    "IpRules": [
      {
        "Ip": "84.247.85.224",
        "Rules": [
          {
            "Endpoint": "*",
            "Period": "1s",
            "Limit": 10
          },
          {
            "Endpoint": "*",
            "Period": "15m",
            "Limit": 200
          }
        ]
      },
      {
        "Ip": "192.168.3.22/25",
        "Rules": [
          {
            "Endpoint": "*",
            "Period": "1s",
            "Limit": 5
          },
          {
            "Endpoint": "*",
            "Period": "15m",
            "Limit": 150
          },
          {
            "Endpoint": "*",
            "Period": "12h",
            "Limit": 500
          }
        ]
      }
    ]
  }
```

### Client Rate Limiting

We can have all the configurations we saw for the IP Rate for the Client Rate, so instead of creating settings for the IP address, we will use ClientId.

```csharp
// configure client rate limiting middleware
builder.Services.Configure<ClientRateLimitOptions>(builder.Configuration.GetSection("ClientRateLimiting"));
builder.Services.Configure<ClientRateLimitPolicies>(builder.Configuration.GetSection("ClientRateLimitPolicies"));
```

```json
  "ClientRateLimiting": {
    "EnableEndpointRateLimiting": true,
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,
    "EndpointWhitelist": [ "*:/api/values", "delete:/api/clients", "get:/" ],
    "ClientWhitelist": [ "cl-key-a", "cl-key-b" ],
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "1s",
        "Limit": 2
      },
      {
        "Endpoint": "*",
        "Period": "1m",
        "Limit": 5
      },
      {
        "Endpoint": "post:/muscles",
        "Period": "5m",
        "Limit": 3
      },
    ]
  },

  "ClientRateLimitPolicies": {
    "ClientRules": [
      {
        "ClientId": "cl-key-1",
        "Rules": [
          {
            "Endpoint": "*",
            "Period": "1s",
            "Limit": 10
          },
          {
            "Endpoint": "get:/muscles",
            "Period": "1m",
            "Limit": 2
          },
          {
            "Endpoint": "put:/muscles",
            "Period": "5m",
            "Limit": 2
          }
        ]
      },
      {
        "ClientId": "cl-key-2",
        "Rules": [
          {
            "Endpoint": "*",
            "Period": "1s",
            "Limit": 10
          },
          {
            "Endpoint": "get:/muscles",
            "Period": "1m",
            "Limit": 0
          },
          {
            "Endpoint": "post:/muscles",
            "Period": "5m",
            "Limit": 50
          }
        ]
      },
      {
        "ClientId": "cl-key-3",
        "Rules": [
          {
            "Endpoint": "post:/muscles",
            "Period": "1s",
            "Limit": 3
          }
        ]
      }
    ]
  }
```

## Custom Rate Limit configuration

The AspNetCoreRateLimit middleware supports customization in case we want to use a different header or the value we want to configure is present in the query.

For example, for this call, `https://localhost:7218/muscles?X-ClientId=dev-id-1,` even having the `dev-id-1` value as `ClientWhitelist`, the middleware would not apply the setting because it is expecting it as a header.

To customize, first, we need to create a class that inherits from `RateLimitConfiguration` class and then create another class that implements the interface `IClientResolveContributor` so we can get the value from the query parameters. We also need to add it in the `ClientResolvers` property of `CustomRateLimitConfiguration` class.

```csharp
public class CustomRateLimitConfiguration : RateLimitConfiguration
{
    public CustomRateLimitConfiguration(IOptions<IpRateLimitOptions> ipOptions, IOptions<ClientRateLimitOptions> clientOptions) : base(ipOptions, clientOptions)
    {
    }

    public override void RegisterResolvers()
    {
        base.RegisterResolvers();
        ClientResolvers.Add(new QueryStringClientIdResolveContributor());
    }
}

public class QueryStringClientIdResolveContributor : IClientResolveContributor
{
    public Task<string> ResolveClientAsync(HttpContext httpContext)
    {
        return Task.FromResult<string>(httpContext.Request.Query["X-ClientId"]);
    }
}
```

To finalize, we need to tell the application to use our custom implementation.

```csharp
//builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
builder.Services.AddSingleton<IRateLimitConfiguration, CustomRateLimitConfiguration>();
```

# **Wrapping Up**

In this [link](https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/), you can see that built-in Rate Limiting support is now part of .NET 7.

You can find the complete code on my [GitHub](https://github.com/jhonatanfernando/coachplan).
