ASP.NET Core's rate limiting middleware offers four limiters: fixed window, sliding window, token bucket and concurrency. Use fixed window for simple per client caps, token bucket when short bursts are fine, sliding window for smoother enforcement, and concurrency for expensive endpoints. Choose by how costly the endpoint is.
What the middleware is
The Microsoft.AspNetCore.RateLimiting middleware lets an app define rate limiting policies and attach them to endpoints. You register policies with AddRateLimiter, turn the middleware on with UseRateLimiter, and attach a named policy with RequireRateLimiting or the EnableRateLimiting attribute. A global limiter set through options applies to all endpoints automatically.
The four algorithms
| Limiter | How it limits | Suits |
|---|---|---|
| Fixed window | A set number of requests per time window, then the count resets | Simple caps such as login attempts |
| Sliding window | Like fixed window but the window is split into segments, and requests from the expired segment are added back as it slides | Smoother limits without the burst at a window edge |
| Token bucket | A fixed number of tokens added each replenishment period, up to a bucket limit | APIs that allow bursts but control the average |
| Concurrency | Limits only simultaneous requests, not requests per period | Costly endpoints such as reports or exports |
The docs note that the fixed, sliding and token limiters cap requests over a period, while the concurrency limiter does not. They also advise considering endpoint cost, meaning the time, data access, CPU and I/O it uses, when you choose.
Partitioning, and its risk
Partitions give each key its own counter, so one user cannot consume everyone's allowance. Keys can be user identity, IP address, API key or path.
Microsoft's page warns about this directly. If you create partitions from user input, the app becomes vulnerable to denial of service. For example, partitioning on client IP leaves you open to attacks that use source address spoofing. Another note says each distinct key creates and caches its own limiter, so partitioning on unbounded user controlled input can exhaust memory. Prefer authenticated identity where available, and bound the number of keys.
Rejections and responses
The docs show setting RejectionStatusCode to 429 Too Many Requests, or an OnRejected callback that sets the status and a Retry-After header from limiter metadata. Not every limiter provides retry timing. The page notes the concurrency limiter does not.
builder.Services.AddRateLimiter(o =>
{
o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
o.AddFixedWindowLimiter("login", p =>
{
p.PermitLimit = 5;
p.Window = TimeSpan.FromMinutes(1);
p.QueueLimit = 0;
});
});
app.UseRateLimiter();
app.MapPost("/login", Login).RequireRateLimiting("login");
One ordering note from the docs: when you use endpoint specific policies or attributes, UseRateLimiter must be called after UseRouting.
It is not DDoS protection
The documentation states that rate limiting helps reduce denial of service risk but is not a comprehensive solution for distributed attacks, and suggests a commercial protection service or the DDoS features of your cloud host alongside it. Treat the middleware as application level fairness and abuse control.
Steps
- Rank endpoints by cost and by abuse value, such as login, password reset, search and export.
- Pick a limiter per class, and a conservative global default.
- Partition by authenticated identity first, then by IP for anonymous traffic.
- Return 429 with Retry-After where the limiter supplies it.
- Load test, as the docs advise, before deploying.
- Review how the client IP is determined behind proxies.
Try it yourself
Click through it at your own pace, or open it full screen. Look for how missing rate limiting appears as an API finding.
Quick check
Should every endpoint use the same limit?
No. Login and password reset need tight limits, while read endpoints can be looser. A global default plus named policies covers both.
Does rate limiting stop credential stuffing?
It slows it. Pair it with multi factor authentication, breached password checks and monitoring.
Safeguard dependency scanning tracks the NuGet packages behind your ASP.NET Core API, and the free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.