ASP.NET Core  

Implementing Rate Limiting for ASP.NET Core APIs

As APIs become the backbone of modern applications, protecting them against abuse, denial-of-service attacks, and excessive requests is critical. Rate limiting is a widely used strategy that controls how many requests a client can make within a specific time window. Implementing rate limiting ensures fair usage, improves performance, and protects backend services from overload.

In this article, we will explore how to implement rate limiting in ASP.NET Core APIs using real-world practices, focusing on maintainability, scalability, and production readiness.

Table of Contents

  1. Introduction to Rate Limiting

  2. Benefits of Rate Limiting

  3. Common Rate Limiting Strategies

  4. Architecture Overview

  5. Setting Up ASP.NET Core API Project

  6. Implementing Rate Limiting Using Middleware

  7. Using ASP.NET Core Built-in Rate Limiting

  8. Advanced Strategies: IP-Based and User-Based Limits

  9. Logging, Monitoring, and Alerts

  10. Best Practices for Production

  11. Testing and Performance Considerations

  12. Conclusion

1. Introduction to Rate Limiting

Rate limiting restricts the number of requests a client can send to a server in a given time period. It is essential to:

  • Prevent abuse by malicious users or bots

  • Reduce server overload and improve stability

  • Protect sensitive endpoints and maintain QoS (Quality of Service)

Without proper rate limiting, APIs can be vulnerable to DDoS attacks or accidental spikes, affecting the availability for legitimate users.

2. Benefits of Rate Limiting

Some key benefits include:

  • Enhanced security: Mitigates brute-force login attacks or API scraping.

  • Improved performance: Reduces backend load and prevents resource exhaustion.

  • Fair usage: Ensures all clients get equitable access to resources.

  • Cost control: Limits excessive requests to paid APIs or services.

3. Common Rate Limiting Strategies

There are several strategies to implement rate limiting:

  1. Fixed Window: Allows a fixed number of requests per time window. Simple but can burst at window boundaries.

  2. Sliding Window: Uses a moving window for smoother rate limiting.

  3. Token Bucket: Tokens are added at a fixed rate; requests consume tokens. Allows short bursts.

  4. Leaky Bucket: Requests are processed at a steady rate; excess requests are queued or dropped.

For APIs, Token Bucket and Sliding Window are commonly preferred due to their flexibility.

4. Architecture Overview

A scalable rate limiting architecture involves:

  • Middleware Layer: Intercepts requests and enforces limits.

  • Storage Layer: Stores request counts (in-memory, Redis, or distributed cache).

  • API Layer: Business logic is protected by rate limiting.

  • Monitoring: Logs and metrics for analytics and alerts.

In production, Redis or distributed caches are preferred to handle multiple server instances.

5. Setting Up ASP.NET Core API Project

Start by creating an ASP.NET Core Web API:

dotnet new webapi -n RateLimitAPI
cd RateLimitAPI

Add Redis (optional for distributed rate limiting):

dotnet add package StackExchange.Redis

6. Implementing Rate Limiting Using Middleware

Custom middleware is a flexible way to implement rate limiting.

a. Create Rate Limiting Middleware

public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
    private readonly int _limit = 5; // Max requests
    private readonly TimeSpan _period = TimeSpan.FromMinutes(1);

    public RateLimitingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var ipAddress = context.Connection.RemoteIpAddress.ToString();
        var cacheKey = $"rl_{ipAddress}";

        if (!_cache.TryGetValue(cacheKey, out int requestCount))
        {
            requestCount = 0;
        }

        if (requestCount >= _limit)
        {
            context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            await context.Response.WriteAsync("Rate limit exceeded. Try again later.");
            return;
        }

        _cache.Set(cacheKey, requestCount + 1, _period);
        await _next(context);
    }
}

b. Register Middleware

app.UseMiddleware<RateLimitingMiddleware>();

This simple middleware enforces a limit of 5 requests per minute per IP address.

7. Using ASP.NET Core Built-in Rate Limiting

Starting from .NET 7, ASP.NET Core has built-in rate limiting with Microsoft.AspNetCore.RateLimiting package.

a. Install Package

dotnet add package Microsoft.AspNetCore.RateLimiting

b. Configure Rate Limiting in Program.cs

using Microsoft.AspNetCore.RateLimiting;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("Fixed", config =>
    {
        config.PermitLimit = 10;
        config.Window = TimeSpan.FromMinutes(1);
        config.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        config.QueueLimit = 2;
    });
});

var app = builder.Build();
app.UseRateLimiter();

c. Apply to Specific Endpoints

app.MapGet("/api/data", () => "Hello World").RequireRateLimiting("Fixed");

The built-in solution is production-ready, supports queuing, and can integrate with distributed caches.

8. Advanced Strategies: IP-Based and User-Based Limits

a. IP-Based Rate Limiting

  • Restrict requests per IP for public APIs.

  • Use middleware or distributed caches like Redis.

b. User-Based Rate Limiting

  • Apply limits per user ID or API key.

  • Useful for authenticated endpoints where clients share the same IP.

Example using Redis

var count = await redis.StringIncrementAsync($"rl_{userId}");
if (count == 1)
    await redis.KeyExpireAsync($"rl_{userId}", TimeSpan.FromMinutes(1));

if (count > 100)
{
    context.Response.StatusCode = 429;
    await context.Response.WriteAsync("Rate limit exceeded");
    return;
}

9. Logging, Monitoring, and Alerts

To manage rate limits effectively, logging and monitoring are crucial:

  • Log rate-limited requests with IP, user ID, and endpoint.

  • Use Application Insights, Prometheus, or ELK Stack for monitoring.

  • Set alerts for repeated violations, spikes, or DDoS attempts.

_logger.LogWarning("Rate limit exceeded for IP {IP}", context.Connection.RemoteIpAddress);

10. Best Practices for Production

  1. Use distributed caches like Redis for multi-server environments.

  2. Different limits for different endpoints: Sensitive operations can have stricter limits.

  3. Return appropriate headers: Inform clients of remaining requests.

    • X-RateLimit-Limit

    • X-RateLimit-Remaining

    • Retry-After

  4. Graceful degradation: Queue requests or provide fallback messages instead of outright rejecting.

  5. Combine with authentication: Use API keys or JWTs to enforce per-user limits.

11. Testing and Performance Considerations

  • Test limits under load with tools like Postman Runner or JMeter.

  • Monitor memory usage if using in-memory counters for large-scale apps.

  • Validate headers to ensure clients receive accurate limit information.

  • Simulate burst traffic to verify sliding window or token bucket implementations.

Conclusion

Rate limiting is a critical component of secure and stable APIs. By controlling request rates, you protect your system from abuse, improve performance, and ensure fair usage. ASP.NET Core provides both custom middleware options and built-in rate limiting features that are production-ready.

Key takeaways

  • Use built-in rate limiting for simplicity and maintainability.

  • For multi-server environments, Redis-based counters provide distributed rate limiting.

  • Always monitor, log, and alert for rate limit violations.

  • Combine IP-based and user-based strategies for optimal security.

With a well-implemented rate limiting strategy, your APIs can handle high traffic securely, reliably, and efficiently.