Core API Rate Limiting

Without rate limiting, a single client could flood the BookStore API with thousands of requests per second — overloading the server, exhausting the database, and degrading service for all other users. Rate limiting restricts how many requests a client can make in a given time window and returns a 429 Too Many Requests response when the limit is exceeded.

What Is Rate Limiting?

Without Rate Limiting:
  Client A sends 10,000 requests/min → Server crashes

With Rate Limiting:
  Client A sends 10,000 requests/min
    → First 100 requests: 200 OK ✓
    → Request 101+: 429 Too Many Requests ✗
    → Wait 1 minute → limit resets

ASP.NET Core Built-in Rate Limiting (.NET 7+)

Starting from .NET 7, rate limiting is built into ASP.NET Core — no extra package is needed.

Step 1 – Register Rate Limiting in Program.cs

// Program.cs
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;

builder.Services.AddRateLimiter(options =>
{
    // Policy 1: Fixed window — 100 requests per minute for general endpoints
    options.AddFixedWindowLimiter("GeneralPolicy", config =>
    {
        config.PermitLimit = 100;
        config.Window = TimeSpan.FromMinutes(1);
        config.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        config.QueueLimit = 5;
    });

    // Policy 2: Sliding window — 50 requests per minute for write operations
    options.AddSlidingWindowLimiter("WritePolicy", config =>
    {
        config.PermitLimit = 50;
        config.Window = TimeSpan.FromMinutes(1);
        config.SegmentsPerWindow = 6;   // checks every 10 seconds
        config.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        config.QueueLimit = 2;
    });

    // Policy 3: Token bucket — allows short bursts but enforces average rate
    options.AddTokenBucketLimiter("BurstPolicy", config =>
    {
        config.TokenLimit = 20;              // max 20 tokens
        config.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        config.QueueLimit = 5;
        config.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
        config.TokensPerPeriod = 5;         // add 5 tokens every 10 seconds
        config.AutoReplenishment = true;
    });

    // Custom response when rate limit is exceeded
    options.OnRejected = async (context, token) =>
    {
        context.HttpContext.Response.StatusCode = 429;
        context.HttpContext.Response.ContentType = "application/json";

        await context.HttpContext.Response.WriteAsJsonAsync(new
        {
            statusCode = 429,
            message = "Too many requests. Please slow down and try again later.",
            retryAfter = "60 seconds"
        }, cancellationToken: token);
    };
});

Step 2 – Apply Rate Limiting in the Middleware Pipeline

// Program.cs
var app = builder.Build();

app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseHttpsRedirection();
app.UseCors("BookStorePolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();   // ← add rate limiting before controller mapping
app.MapControllers();

app.Run();

Step 3 – Apply Policies to Controllers and Actions

// Controllers/V2/BooksController.cs
using Microsoft.AspNetCore.RateLimiting;

[ApiController]
[Route("api/v2/[controller]")]
[EnableRateLimiting("GeneralPolicy")]   // ← applies to entire controller
public class BooksController : ControllerBase
{
    // GET /api/v2/books — uses GeneralPolicy (100 req/min)
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> GetAll() { ... }

    // GET /api/v2/books/1 — uses GeneralPolicy (100 req/min)
    [HttpGet("{id:int}")]
    [AllowAnonymous]
    public async Task<IActionResult> GetById(int id) { ... }

    // POST /api/v2/books — stricter WritePolicy (50 req/min)
    [HttpPost]
    [Authorize(Roles = "Admin")]
    [EnableRateLimiting("WritePolicy")]   // ← overrides controller policy
    public async Task<IActionResult> Create([FromBody] BookCreateDto dto) { ... }

    // PUT /api/v2/books/1 — stricter WritePolicy
    [HttpPut("{id:int}")]
    [Authorize(Roles = "Admin")]
    [EnableRateLimiting("WritePolicy")]
    public async Task<IActionResult> Update(int id, [FromBody] BookUpdateDto dto) { ... }

    // DELETE /api/v2/books/1 — strictest BurstPolicy (20 per burst)
    [HttpDelete("{id:int}")]
    [Authorize(Roles = "Admin")]
    [EnableRateLimiting("BurstPolicy")]
    public async Task<IActionResult> Delete(int id) { ... }
}

Rate Limiting Algorithms Compared

AlgorithmHow It WorksBookStore Use Case
Fixed WindowCounts requests in fixed time windows (0-60s, 60-120s). Resets at window boundary.General GET endpoints (100/min)
Sliding WindowCounts requests over a rolling window. Smoother than fixed window.Write operations (POST/PUT)
Token BucketBucket refills at a fixed rate. Allows short bursts when bucket is full.Admin delete operations
ConcurrencyLimits simultaneous requests, not rate.Expensive search operations

Rate Limit Exceeded Response

POST /api/v2/books  (101st request in 1 minute)

Response: 429 Too Many Requests
Retry-After: 60

{
  "statusCode": 429,
  "message": "Too many requests. Please slow down and try again later.",
  "retryAfter": "60 seconds"
}

Per-User Rate Limiting

The examples above limit requests per IP address. For authenticated users, rate limits can be set per user ID from the JWT token:

// Program.cs — per-user rate limiting based on JWT claims
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("PerUserPolicy", config =>
    {
        config.PermitLimit = 200;
        config.Window = TimeSpan.FromMinutes(1);
    });

    // Partition the rate limit by user ID claim
    options.AddPolicy("AuthenticatedUserPolicy", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.FindFirst("Id")?.Value ?? httpContext.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }));
});

Disabling Rate Limiting on Specific Endpoints

[HttpGet("health")]
[DisableRateLimiting]           // ← health check endpoint, no limit needed
public IActionResult Health()
{
    return Ok(new { status = "Healthy", timestamp = DateTime.UtcNow });
}

Rate Limiting Strategy for BookStore API

Endpoint                   Policy          Limit
────────────────────────────────────────────────────────────
GET  /api/v2/books         GeneralPolicy   100 req/min
GET  /api/v2/books/{id}    GeneralPolicy   100 req/min
POST /api/v2/books         WritePolicy     50 req/min
PUT  /api/v2/books/{id}    WritePolicy     50 req/min
DELETE /api/v2/books/{id}  BurstPolicy     20 per burst
POST /api/auth/login       WritePolicy     50 req/min (prevent brute force)
GET  /health               No limit        Unlimited

Key Points

  • Rate limiting prevents abuse and protects the server from being overwhelmed by too many requests.
  • ASP.NET Core .NET 7+ has built-in rate limiting with four algorithms: Fixed Window, Sliding Window, Token Bucket, and Concurrency.
  • Different policies can be applied to different endpoints — stricter limits on write operations, more lenient on reads.
  • When a limit is exceeded, a 429 Too Many Requests response is returned with a Retry-After header.
  • Rate limiting can partition by IP address (anonymous users) or by user ID (authenticated users).

Leave a Comment