# Advanced Rate Limiting Use Cases In .NET

> Rate limiting is about restricting the number of requests to your application. It improves security, guards against DDoS attacks, and prevents overloading your servers. I'll show you how to rate limit users by IP address, by their identity, and on the reverse proxy.

Published: 2023-08-19. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/advanced-rate-limiting-use-cases-in-dotnet

The built-in .NET rate limiter goes beyond a single global policy once you partition it.
A `RateLimitPartition` lets you key the limit on the client's IP address for anonymous users, or on the user identity for authenticated ones.
If you run behind a reverse proxy, you partition on the `X-Forwarded-For` header instead of the connection IP, or apply rate limiting at the proxy itself.

**Rate limiting** is about restricting the number of requests to your application.
It's usually applied within a specific time window or based on other criteria.

It's helpful for a few reasons:

- Improves security
- Guards against DDoS attacks
- Prevents overloading of application servers
- Reduces costs by preventing unnecessary resource consumption

**.NET 7** shipped with a **built-in rate limiter**, but you need to know how to implement it correctly.
Or you could grind your system to a halt - and we don't want that.

In this week's newsletter, I'll teach you:

- How to rate limit users by **IP address**
- How to rate limit users by their **identity**
- How to apply **rate limiting** on the **reverse proxy**

So let's dive in!

## Built-In Rate Limiting In .NET 7

Starting with .NET 7, we have access to built-in **rate limiting middleware** in the `Microsoft.AspNetCore.RateLimiting` namespace.
The API is straightforward, and you can create a rate limit policy with a few lines of code.

We can use one of the four **rate limiting algorithms**:

- Fixed window
- Sliding window
- Token bucket
- Concurrency

Here's how to define a **rate limit policy** by calling the `AddTokenBucketLimiter` method:

```csharp
builder.Services.AddRateLimiter(rateLimiterOptions =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    rateLimiterOptions.AddTokenBucketLimiter("token", options =>
    {
        options.TokenLimit = 1000;
        options.ReplenishmentPeriod = TimeSpan.FromHours(1);
        options.TokensPerPeriod = 700;
        options.AutoReplenishment = true;
    });
});
```

Now you can reference the `token` rate limit policy on your endpoint or controller.

You also have to add the `RateLimitingMiddleware` to the request pipeline:

```csharp
app.UseRateLimiter();
```

You can learn more about [**rate limiting in .NET 7 here,**](https://milanjovanovic.tech/blog/how-to-use-rate-limiting-in-aspnet-core) so I won't go deeper into the fundamentals.

## Rate Limiting Users By IP Address

The approach I just showed you has a **problem** - the **rate limit policy** is global and **applies to all users**.

Most of the time, you don't want to do this.
Rate limiting should be granular and apply to **individual users**.

Luckily, you can achieve this by creating a `RateLimitPartition`.

The `RateLimitPartition` has two components:

- Partition key
- Rate limiter policy

Here's how to define a rate limiter with a fixed window policy, and the **partition key** is the user's **IP address**.

```csharp {5}
builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("fixed-by-ip", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1)
            }));
});
```

Rate limiting by **IP address** can be a good layer of security for **unauthenticated users**.
You don't know who is accessing your system and can't apply more granular rate limiting.
This can help protect your system from malicious users trying to perform a DDoS attack.

You can also [**create chained limiters**](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-7.0#create-chained-limiters) using the `CreateChained` API.
It allows you to pass in multiple `PartitionedRateLimiter`, which are combined into one `PartitionedRateLimiter`.
The chained limiter runs all the input limiters in sequence (one by one).

If your application is running behind a **reverse proxy**, you need to make sure not to rate limit the proxy IP address.
Reverse proxies usually **forward** the original IP address with the `X-Forwarded-For` header.
So you can use it as the **partition key**:

```csharp {5}
builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("fixed-by-ip", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            httpContext.Request.Headers["X-Forwarded-For"].ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1)
            }));
});
```

## Rate Limiting Users By Identity

If you require users to **authenticate** with your API, you can determine who the current is.
Then you can use the user's **identity** as the **partition key** for a `RateLimitPartition`.

Here's how you would create such a rate limit policy:

```csharp {5}
builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("fixed-by-user", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name?.ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromMinutes(1)
            }));
});
```

I'm using the `User.Identity` value on the `HttpContext` to get the current user's `Name` claim.
This usually corresponds to the `sub` claim inside a JWT - which is the user identifier.

## Applying Rate Limting On The Reverse Proxy

In a robust implementation, you want to **rate limit** on the **reverse proxy** level before the request hits your API.
And if you have a distributed system, this is a requirement.
Otherwise, your system wouldn't function correctly.

There are many reverse proxy implementations to choose from.

**YARP** is a reverse proxy with excellent .NET integration.
Not surprising since it was written in C#.
You can learn more about [**building an API Gateway with YARP here.**](https://milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp)

To implement rate limiting on the reverse proxy with **YARP** you need to:

- Define a rate limit policy (covered in previous examples)
- Configure the `RateLimiterPolicy` for the route in YARP settings

```json
"products-route": {
  "ClusterId": "products-cluster",
  "RateLimiterPolicy": "sixty-per-minute-fixed",
  "Match": {
    "Path": "/products/{**catch-all}"
  },
  "Transforms": [
    { "PathPattern": "{**catch-all}" }
  ]
}
```

The built-in rate limiter middleware uses an **in-memory** store to track the number of requests.
If you want to run your reverse proxy in a high-availability setup, you will need to use a **distributed cache**.
A nice option to look into is using a [**Redis backplane for rate limiting.**](https://github.com/cristipufu/aspnetcore-redis-rate-limiting)

## Closing Thoughts

With the `PartitionedRateLimiter` you can easily create granular rate limit policies.

The two common approaches are:

- Rate limiting by **IP address**
- Rate limiting by the **user identifier**

I was really excited to see the .NET team ship rate limiting.
But, the current implementation has its shortcomings.
The main issue is that it only works **in memory**.
For a **distributed** solution, you need to implement something yourself or use an external library.

You can use the **YARP** reverse proxy to build robust and scalable distributed systems.
And it only takes a few lines of code to add **rate limiting** on the reverse proxy level.
I'm using it extensively in my systems.

Thanks for reading.

And stay awesome!

---

## Frequently asked questions

### What is rate limiting and why should you use it?

Rate limiting restricts the number of requests to your application, usually within a specific time window or based on other criteria. It improves security, guards against DDoS attacks, prevents overloading your application servers, and reduces costs by cutting unnecessary resource consumption.

### Does .NET have built-in rate limiting?

Yes. .NET 7 shipped built-in rate limiting middleware in the Microsoft.AspNetCore.RateLimiting namespace. You define a policy with one of four algorithms (fixed window, sliding window, token bucket, or concurrency), reference it on an endpoint or controller, and add UseRateLimiter to the pipeline.

### How do you rate limit users by IP address in ASP.NET Core?

Create a RateLimitPartition with the client's IP address as the partition key, for example a fixed window limiter keyed on RemoteIpAddress. This gives unauthenticated users a granular limit instead of one global policy, and it adds a layer of protection against DDoS attempts.

### How do you rate limit by IP when running behind a reverse proxy?

Do not use the connection IP, because that would rate limit the proxy itself. Reverse proxies usually forward the original client IP in the X-Forwarded-For header, so use that header value as the partition key for the rate limit policy.

### How do you rate limit authenticated users in .NET?

Use the user's identity as the partition key of a RateLimitPartition. Reading User.Identity.Name from the HttpContext usually corresponds to the sub claim inside a JWT, the user identifier, so each authenticated user gets their own rate limit window.

### Does the built-in .NET rate limiter work in a distributed system?

Not on its own. The built-in middleware that came with .NET 7 tracks requests in memory, which was its main shortcoming. For a distributed or high-availability setup you need a distributed cache, such as a Redis backplane library, or rate limiting at the reverse proxy level with a shared store.
