Skip to content

Rate limiting /1 #26756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d1c89f3
Rate limiting /1
Rick-Anderson Aug 17, 2022
db6f8d1
Rate limiting /1
Rick-Anderson Aug 17, 2022
fef267f
Rate limiting /1
Rick-Anderson Aug 17, 2022
4b0520d
Rate limiting /1
Rick-Anderson Aug 18, 2022
f18a387
Rate limiting /1
Rick-Anderson Aug 18, 2022
21db92d
Rate limiting /1
Rick-Anderson Aug 18, 2022
daab07a
Rate limiting /1
Rick-Anderson Aug 18, 2022
716bbad
Rate limiting /1
Rick-Anderson Aug 18, 2022
f083c24
work
Rick-Anderson Aug 18, 2022
06000be
work
Rick-Anderson Aug 18, 2022
0e4f096
work
Rick-Anderson Aug 19, 2022
486f245
work
Rick-Anderson Aug 19, 2022
55e14cf
work
Rick-Anderson Aug 19, 2022
753e4a3
work
Rick-Anderson Aug 19, 2022
d71528b
work
Rick-Anderson Aug 19, 2022
0ad7426
work
Rick-Anderson Aug 19, 2022
6c1a217
work
Rick-Anderson Aug 19, 2022
b5a2b92
work
Rick-Anderson Aug 19, 2022
495149e
work
Rick-Anderson Aug 20, 2022
d8bc410
work
Rick-Anderson Aug 20, 2022
6c68bf1
work
Rick-Anderson Aug 20, 2022
3581f84
work
Rick-Anderson Aug 20, 2022
d16a43f
work
Rick-Anderson Aug 20, 2022
77f6d85
work
Rick-Anderson Aug 23, 2022
c299019
work
Rick-Anderson Aug 24, 2022
9cececb
work
Rick-Anderson Aug 24, 2022
af5544c
work
Rick-Anderson Aug 24, 2022
241c32d
work
Rick-Anderson Aug 24, 2022
572cd61
work
Rick-Anderson Aug 24, 2022
0eb3de5
work
Rick-Anderson Aug 24, 2022
ab96f6b
work
Rick-Anderson Aug 24, 2022
248ab17
work
Rick-Anderson Aug 24, 2022
87644ab
work
Rick-Anderson Aug 24, 2022
2fdd988
Apply suggestions from code review
Rick-Anderson Aug 25, 2022
c7f1674
react to feedback
Rick-Anderson Aug 25, 2022
e95298f
react to feedback
Rick-Anderson Aug 25, 2022
1358540
react to feedback
Rick-Anderson Aug 26, 2022
c7bc1ef
react to feedback
Rick-Anderson Aug 30, 2022
e0b9597
Apply suggestions from code review
Rick-Anderson Aug 30, 2022
6c155ec
react to feedback
Rick-Anderson Aug 30, 2022
24732c5
react to feedback
Rick-Anderson Aug 30, 2022
fa17ebf
react to feedback
Rick-Anderson Aug 30, 2022
df0fa51
react to feedback
Rick-Anderson Aug 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions aspnetcore/performance/rate-limit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
title: Rate limiting middleware in ASP.NET Core
author: rick-anderson
description: Learn how limit requests in ASP.NET Core apps
ms.author: riande
ms.custom: mvc
ms.date: 8/28/2022
uid: performance/rate-limit
---

# Rate limiting middleware in ASP.NET Core

By [Arvin Kahbazi](https://github.com/Kahbazi) and [Rick Anderson](https://twitter.com/RickAndMSFT)

:::moniker range=">= aspnetcore-7.0"

The `Microsoft.AspNetCore.RateLimiting` middleware provides rate limiting middleware. Apps configure rate limiting policies and then attach the policies to endpoints. Apps using rate limiting should be carefully load tested and reviewed before deploying. See [Testing endpoints with rate limiting](#test7) in this article for more information.

## Rate limiter algorithms

The [`RateLimiterOptionsExtensions`](/dotnet/api/microsoft.aspnetcore.ratelimiting.ratelimiteroptionsextensions) class provides the following extension methods for rate limiting:

* [Fixed window](#fixed)
* [Sliding window](#slide)
* [Token bucket](#token)
* [Concurrency](#concur)

<a name="fixed"></a>

### Fixed window limiter

The [`AddFixedWindowLimiter`](/dotnet/api/microsoft.aspnetcore.ratelimiting.ratelimiteroptionsextensions.addfixedwindowlimiter#microsoft-aspnetcore-ratelimiting-ratelimiteroptionsextensions-addfixedwindowlimiter(microsoft-aspnetcore-ratelimiting-ratelimiteroptions-system-string-system-threading-ratelimiting-fixedwindowratelimiteroptions)) method uses a fixed time window to limit requests. When the time window expires, a new time window starts and the request limit is reset.

Consider the following code:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_fixed":::

The preceding code:
* Calls [UseRateLimiter](/dotnet/api/microsoft.aspnetcore.builder.ratelimiterapplicationbuilderextensions.useratelimiter) to enable rate limiting.
* Creates a fixed window limiter with a policy name of `"fixed"` and sets:
* `permitLimit` to 4 and the time `window` to 12. A maximum of 4 requests per each 12-second window are allowed.
* `queueProcessingOrder` to `QueueProcessingOrder.OldestFirst`.
* `queueLimit` to 2.

Apps should use [Configuration](xref:fundamentals/configuration/index) to set limiter options. The following code updates the preceding code using [`MyRateLimitOptions`](https://github.com/dotnet/AspNetCore.Docs.Samples/blob/main/fundamentals/middleware/rate-limit/WebRateLimitAuth/Models/MyRateLimitOptions.cs) for configuration:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_fixed2":::

<a name="slide"></a>

### Sliding window limiter

A sliding window algorithm:

* Is similar to the fixed window limiter but adds segments per window. The window slides one segment each segment interval. The segment interval is (window time)/(segments per window).
* Limits the requests for a window to `permitLimit` requests.
* Each time window is divided in `n` segments per window.
* Requests taken from the expired time segment one window back (`n` segments prior to the current segment), are added to the current segment. We refer to the most expired time segment one window back as the expired segment. Consider the following table which shows a sliding window limiter with a 30-second window, 3 segments per window and a limit of 100 requests:

* The top row and first column shows the time segment.
* The second row shows the remaining requests available. The remaining requests are available-requests+recycled.
* Requests at each time moves along the diagonal blue line.
* From time 30 on, the request taken from the expired time segment are added back to the request limit, as shown in the red lines.

![Table showing requests, limits, and recycled slots](~/performance/rate-limit/_static/rate.png)

<!--
| Time | 0 | 10 | 20 | 30 | 40 | 50 | 60 |
| ---- | -- | -- | -- | -- | -- | -- | -- |
| Available | 100-20+0=80 | 80-30+0=50 | 50-40+0=10 | 10-30+20=0 |0+30-10=20 | 20-10+40=50 | 50-35+30=45 |
| 0 | -20 | | | | | | |
| 10 | | -30 | | | | | |
| 20 | | | -40 | | | | |
| 30 | **[+20]** | | | -30 | | | |
| 40 | |**[+30]**| | | -10 | | |
| 50 | | | **[+40]** | | | -10 | |
| 60 | | | | **[+30]** | | | -35|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this table is too confusing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something like my updated table? If you don't like that approach, I'll delete it. I also added a couple bullets explaining the blue and red lines.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe slightly better, still not a fan though. Maybe let some other folks look at it and see if they understand?

I'm slightly biased, but I like how the algorithm was displayed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe slightly better, still not a fan though. Maybe let some other folks look at it and see if they understand?

I'm slightly biased, but I like how the algorithm was displayed here.

I had a hard time following that, it struck me as a diagram written by someone very familiar with the algorithm. At any rate the explanation and diagrams belong in dotnet/docs#30426 which @IEvangelist owns, so he can own that.

IMAO, rate limiting is a gamechanger so I'm going to merge this so it can be live. I'll tweat it with a challenge to come up with a better diagram.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2022-08-30 13 39 11
@BrennanConroy I tried the preceding prototype but it looked worse.


-->

The following table shows the data in the previous graph in a different format. The **Remaining** column shows the requests available from the previous segment (The **Carry over** from the previous row). The first row shows 100 available because there's no previous segment:

| Time | Available | Taken | Recycled from expired | Carry over |
| ---- | ---- | ------| ------ | ---- |
| 0 | 100 | 20 | 0 | 80 |
| 10 | 80 | 30 | 0 | 50 |
| 20 | 50 | 40 | 0 | 10 |
| 30 | 10 | 30 | 20 | 0 |
| 40 | 0 | 10 | 30 | 20 |
| 50 | 20 | 10 | 40 | 50 |
| 60 | 50 | 35 | 30 | 45 |

The following code uses the sliding window rate limiter:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_slide":::

<a name="token"></a>

### Token bucket limiter

The token bucket limiter is similar to the sliding window limiter, but rather than adding back the requests taken from the expired segment, a fixed number of tokens are added each replenishment period. The tokens added each segment can't increase the available tokens to a number higher than the token bucket limit. The following table shows a token bucket limiter with a limit of 100 tokens and a 10-second replenishment period:

| Time | Available | Taken | Added | Carry over |
| ---- | ---- | ------| ------| ---- |
| 0 | 100 | 20 | 0 | 80 |
| 10 | 80 | 10 | 20 | 90 |
| 20 | 90 | 5 | 15 | 100 |
| 30 | 100 | 30 | 20 | 90 |
| 40 | 90 | 6 | 16 | 100 |
| 50 | 100 | 40 | 20 | 80 |
| 60 | 80 | 50 | 20 | 50 |

The following code uses the token bucket limiter:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_token":::

When `autoReplenishment` is set to `true`, an internal timer replenishes the tokens every `replenishmentPeriod`; when set to `false`, the app must call `TryReplenish` on the limiter.

<a name="concur"></a>

### Concurrency limiter

The concurrency limiter limits the number concurrent requests. Each request reduces the concurrency limit by one. When a request completes, the limit is increased by one. Unlike the other requests limiters that limit the total number of requests for a specified period, the concurrency limiter limits only the number of concurrent requests and doesn't cap the number of requests in a time period.

The following code uses the concurrency limiter:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_concur":::

## Limiter algorithm comparison

The fixed, sliding, and token limiters all limit the maximum number of requests in a time period. The concurrency limiter limits only the number of concurrent requests and doesn't cap the number of requests in a time period. The cost of an endpoint should be considered when selecting a limiter. The cost of an endpoint includes the resources used, for example, time, data access, CPU, and I/O.

## Rate limiter samples

The following samples aren't meant for production code but are examples on how to use the limiters.

### Limiter with `OnRejected`, `RetryAfter`, and `GlobalLimiter`

The following sample:

* Creates a [RateLimiterOptions.OnRejected](xref:Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected) callback that is called when a request exceeds the specified limit. `retryAfter` can be used with the [`TokenBucketRateLimiter`](https://source.dot.net/#System.Threading.RateLimiting/System/Threading/RateLimiting/TokenBucketRateLimiter.cs), [`FixedWindowLimiter`](https://source.dot.net/#System.Threading.RateLimiting/System/Threading/RateLimiting/FixedWindowRateLimiter.cs), and [`SlidingWindowLimiter`](https://source.dot.net/#System.Threading.RateLimiting/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs) because these algorithms are able to estimate when more permits will be added. The `ConcurrencyLimiter` has no way of calculating when permits will be available.
* Adds the following limiters:

* A `SampleRateLimiterPolicy` which implements the `IRateLimiterPolicy<TPartitionKey>` interface. The `SampleRateLimiterPolicy` class is shown later in this article.
* A `SlidingWindowLimiter`:
* With a partition for each authenticated user.
* One shared partition for all anonymous users.
* A <xref:Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.GlobalLimiter> that is applied to all requests. The global limiter will be executed first, followed by the endpoint-specific limiter, if one exists. The `GlobalLimiter` creates a partition for each <xref:System.Net.IPAddress>.

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet":::

> [!WARNING]
>Creating partitions on client IP addresses makes the app vulnerable to Denial of Service Attacks which employ IP Source Address Spoofing. For more information, see [BCP 38 RFC 2827 Network Ingress Filtering: Defeating Denial of Service Attacks which employ IP Source Address Spoofing](https://www.rfc-editor.org/info/bcp38).

See [the samples repository for the complete `Program.cs`](https://github.com/dotnet/AspNetCore.Docs.Samples/blob/main/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs#L145,L281) file.

The `SampleRateLimiterPolicy` class

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/SampleRateLimiterPolicy.cs" id="snippet_1":::

In the preceding code, <xref:Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected> uses <xref:Microsoft.AspNetCore.RateLimiting.OnRejectedContext> to set the response status to [429 Too Many Requests](https://developer.mozilla.org/docs/Web/HTTP/Status/429). The default rejected status is [503 Service Unavailable](https://developer.mozilla.org/docs/Web/HTTP/Status/503).

### Limiter with authorization

The following sample uses JSON Web Tokens (JWT) and creates a partition with the JWT [access token](https://github.com/dotnet/aspnetcore/blob/fd1891536f27e959d14a140ff9307b6a21191de9/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs#L152-L158). In a production app, the JWT would typically be provided by a server acting as a Security token service (STS). For local development, the dotnet [user-jwts](xref:security/authentication/jwt) command line tool can be used to create and manage app-specific local JWTs.

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_jwt":::

### Limiter with `ConcurrencyLimiter`, `TokenBucketRateLimiter`, and authorization

The following sample:

* Adds a `ConcurrencyLimiter` with a policy name of `"get"` that is used on the Razor Pages.
* Adds a `TokenBucketRateLimiter` with a partition for each authorized user and a partition for all anonymous users.
* Sets [RateLimiterOptions.RejectionStatusCode](xref:Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RejectionStatusCode) to [429 Too Many Requests](https://developer.mozilla.org/docs/Web/HTTP/Status/429).

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs" id="snippet_adm2":::

See [the samples repository for the complete `Program.cs`](https://github.com/dotnet/AspNetCore.Docs.Samples/blob/main/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs#L145,L281) file.

<a name="test7"></a>

## Testing endpoints with rate limiting

Before deploying an app using rate limiting to production, it's a good idea to stress test the app to validate the rate limiters and options used. For example, create a [JMeter script](https://jmeter.apache.org/usermanual/jmeter_proxy_step_by_step.html) with a tool like [BlazeMeter](https://guide.blazemeter.com/hc/articles/207421695-Writing-your-first-JMeter-script) or [Apache JMeter HTTP(S) Test Script Recorder](https://jmeter.apache.org/usermanual/jmeter_proxy_step_by_step.html) and load the script to [Azure Load Testing](/azure/load-testing/overview-what-is-azure-load-testing).

Creating partitions with user input makes the app vulnerable to [Denial of Service](https://www.cisa.gov/uscert/ncas/tips/ST04-015) (DoS) Attacks. For example, creating partitions on client IP addresses makes the app vulnerable to Denial of Service Attacks that employ IP Source Address Spoofing. For more information, see [BCP 38 RFC 2827 Network Ingress Filtering: Defeating Denial of Service Attacks that employ IP Source Address Spoofing](https://www.rfc-editor.org/info/bcp38).

:::moniker-end
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.