Elf.AccessRateLimit 1.0.15

dotnet add package Elf.AccessRateLimit --version 1.0.15
                    
NuGet\Install-Package Elf.AccessRateLimit -Version 1.0.15
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Elf.AccessRateLimit" Version="1.0.15" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Elf.AccessRateLimit" Version="1.0.15" />
                    
Directory.Packages.props
<PackageReference Include="Elf.AccessRateLimit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Elf.AccessRateLimit --version 1.0.15
                    
#r "nuget: Elf.AccessRateLimit, 1.0.15"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Elf.AccessRateLimit@1.0.15
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Elf.AccessRateLimit&version=1.0.15
                    
Install as a Cake Addin
#tool nuget:?package=Elf.AccessRateLimit&version=1.0.15
                    
Install as a Cake Tool

Elf.AccessRateLimit

Distributed, Redis-backed access rate limiting for expensive endpoints in ASP.NET Core (.NET 8).

Overview

Elf.AccessRateLimit protects heavy endpoints (downloads, exports, reports) from abuse by enforcing rate limits across multiple app instances using Redis as the source of truth. It uses atomic Lua scripts for concurrency safety and supports per-endpoint policies, escalation penalties, and simple extension methods.

Key features:

  • Distributed enforcement via StackExchange.Redis
  • Per-endpoint policy selection (attribute or endpoint mapping)
  • Escalating blocks for repeated violations
  • Token-bucket algorithm with atomic Redis Lua script
  • Optional headers and custom response body
  • Structured logging and optional metrics hooks

Requirements

  • .NET 8
  • StackExchange.Redis connection (IConnectionMultiplexer)

Install (NuGet)

NuGet package: https://www.nuget.org/packages/Elf.AccessRateLimit/

dotnet add package Elf.AccessRateLimit

Or add a reference in your project file:

<ItemGroup>
  <PackageReference Include="Elf.AccessRateLimit" Version="1.0.1" />
</ItemGroup>

Quick start (code)

using StackExchange.Redis;
using Elf.AccessRateLimit;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<IConnectionMultiplexer>(
    _ => ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")!));

builder.Services.AddElfAccessRateLimit(options =>
{
    options.DefaultPolicyName = "download";
    options.AddPolicy("download", p =>
    {
        p.WithLimit(10, TimeSpan.FromMinutes(1));
        p.WithKeyResolverSpecs("ip", "user");
        p.WithPenalty(penalty =>
        {
            penalty.ViolationWindow = TimeSpan.FromMinutes(10);
            penalty.Penalties = new List<TimeSpan>
            {
                TimeSpan.FromSeconds(10),
                TimeSpan.FromMinutes(1),
                TimeSpan.FromMinutes(5),
                TimeSpan.FromMinutes(30)
            };
        });
    });
});

var app = builder.Build();

app.UseElfAccessRateLimit();

app.MapGet("/download/{id}", () => Results.Ok())
   .RequireAccessRateLimit("download");

app.Run();

Quick start (appsettings.json)

{
  "Elf": {
    "AccessRateLimit": {
      "DefaultPolicyName": "download",
      "RedisKeyPrefix": "elf:accessrl",
      "AddRateLimitHeaders": true,
      "FailOpen": true,
      "Logging": {
        "Detail": "Detailed"
      },
      "Policies": {
        "download": {
          "LimitPerMinute": 10,
          "KeyStrategy": "ip,user",
          "Penalty": {
            "ViolationWindow": "00:10:00",
            "Penalties": [ "00:00:10", "00:01:00", "00:05:00", "00:30:00" ]
          }
        }
      }
    }
  }
}
builder.Services.AddElfAccessRateLimit(builder.Configuration);

By default, AddElfAccessRateLimit(IConfiguration) binds to Elf:AccessRateLimit. To load settings from a different section, pass the section key explicitly:

builder.Services.AddElfAccessRateLimit("RateLimit");
{
  "RateLimit": {
    "DefaultPolicyName": "download"
  }
}

API usage and integration

Middleware

Register services:

builder.Services.AddElfAccessRateLimit(options => { /* policies */ });

Add the middleware:

app.UseElfAccessRateLimit();

Place the middleware after routing (so endpoint metadata is available) and after auth if limits depend on claims.

Endpoint mapping

app.MapGet("/reports/{id}", () => Results.Ok())
   .RequireAccessRateLimit("download");

You can override the scope or cost:

app.MapGet("/reports/{id}", () => Results.Ok())
   .RequireAccessRateLimit("download", scope: "reports", cost: 5);

Attribute usage (MVC/Web API)

[AccessRateLimit("download", Scope = "reports", Cost = 5)]
[HttpGet("/reports/{id}")]
public IActionResult GetReport(string id) => Ok();

Policies (code)

options.AddPolicy("export", p =>
{
    p.WithLimit(5, TimeSpan.FromMinutes(1));
    p.WithSharedBucket("exports");
    p.ForAuthenticated(10);
    p.ForAnonymous(3);
    p.WithCost(2);
    p.WithKeyResolverSpecs("ip", "header:X-Api-Key");
});

Policies (config)

"Policies": {
  "export": {
    "Limit": 5,
    "Window": "00:01:00",
    "SharedBucket": "exports",
    "AuthenticatedLimit": 10,
    "AnonymousLimit": 3,
    "Cost": 2,
    "KeyStrategy": "ip,header:X-Api-Key"
  }
}

Key strategies

Built-in specs:

  • ip (prefers X-Forwarded-For / X-Real-IP, falls back to RemoteIpAddress)
  • user or user-id (ClaimTypes.NameIdentifier)
  • sub
  • api-key (X-Api-Key header)
  • client-id (X-Client-Id header)
  • claim:<type>
  • header:<name>

Example:

p.WithKeyResolverSpecs("ip", "claim:tenant_id", "header:X-Api-Key");

Custom resolver:

public sealed class CustomResolver : IRateLimitKeyResolver
{
    public ValueTask<string?> ResolveAsync(HttpContext context, CancellationToken token = default)
        => new ValueTask<string?>(context.Request.Headers["X-Custom"].ToString());
}

p.WithKeyResolver(new CustomResolver());

Escalating penalties

Use Penalty to increase block duration for repeated violations:

p.WithPenalty(penalty =>
{
    penalty.ViolationWindow = TimeSpan.FromMinutes(10);
    penalty.Penalties = new List<TimeSpan>
    {
        TimeSpan.FromSeconds(10),
        TimeSpan.FromMinutes(1),
        TimeSpan.FromMinutes(5),
        TimeSpan.FromMinutes(30)
    };
});

Headers and responses

When limited, the middleware returns HTTP 429 with:

  • Retry-After
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

Customize the response:

options.Response.ContentType = "application/json";
options.Response.Body = "{\"error\":\"rate_limited\"}";
options.Response.OnRejected = (ctx, decision) =>
{
    ctx.Response.ContentType = "application/json";
    return ctx.Response.WriteAsync("{\"error\":\"rate_limited\"}");
};

Metrics hooks

Implement and register a metrics handler:

public sealed class RateLimitMetrics : IAccessRateLimitMetrics
{
    public void OnAllowed(AccessRateLimitDecision decision) { }
    public void OnLimited(AccessRateLimitDecision decision) { }
    public void OnBlocked(AccessRateLimitDecision decision) { }
}

builder.Services.AddSingleton<IAccessRateLimitMetrics, RateLimitMetrics>();

Logging

Choose between normal or detailed logs (information level or higher):

options.Logging.Detail = AccessRateLimitLogDetail.Detailed;

Normal logs emit limited/blocked decisions. Detailed logs also include allowed decisions and extra fields.

Whitelisting

options.ExemptWhen = ctx =>
    ctx.User.IsInRole("Admin") ||
    ctx.Connection.RemoteIpAddress?.ToString() == "10.0.0.1";

Notes

  • Redis is the source of truth; optional local caching is not required.
  • FailOpen = true allows requests if Redis is unavailable (logged as error).
  • Use SharedBucket or per-request scope to share a limit across multiple endpoints.
  • Cost lets heavy endpoints consume more tokens per request.

Samples

The console app Elf.AccessRateLimit.Samples spins up a local in-process WebApplication and exercises the limiter.

dotnet run --project Elf.AccessRateLimit.Samples -- --redis localhost:6379 --sample all

Samples available: all, basic, keys, escalation.

Folder layout

  • Configuration: options and validation
  • Policies: policy models and provider
  • Keys: key resolvers and key utilities
  • Store: Redis Lua store
  • Middleware: rate limit pipeline
  • Extensions: service/middleware/endpoint registration
  • Metadata: endpoint metadata + attribute
  • Metrics: metrics contracts
  • Models: decision model

Design

The limiter runs in middleware, resolves a policy per endpoint, builds a stable caller key, and evaluates a token bucket in Redis with an atomic Lua script. The Redis result drives allow/deny, headers, logging, and escalation penalties.

Key points that enforce the limit:

  • Policy resolution chooses the endpoint policy or the configured default.
  • Key resolution uses policy resolvers (IP, header, claim, composite) and hashes the result before Redis.
  • Bucket scope defaults to the route pattern, but can be overridden to share limits.
  • Redis Lua script checks blocks, refills tokens, applies cost, and increments violations safely.
  • Escalation penalties set a block key with growing TTLs for repeated violations.
  • Responses return 429 with Retry-After and optional rate limit headers.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Elf.AccessRateLimit:

Package Downloads
Elf.AccessRateLimit.Samples

Light samples to demonstrate the AccessRateLimit lib behavior

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.15 117 12/31/2025
1.0.14 111 12/31/2025
1.0.13 147 12/31/2025
1.0.12 111 12/31/2025
1.0.11 122 12/31/2025
1.0.10 132 12/29/2025
1.0.9 116 12/29/2025
1.0.8 106 12/29/2025
1.0.7 110 12/28/2025
1.0.4 109 12/28/2025
1.0.1 92 12/28/2025