Securing Public APIs in .NET Core

API Security Considerations

When designing APIs, incorporating robust security measures demonstrates both professional rigor and code sophistication.

While authorization via attributes like [Authorize] is commonly used to enforce access control, many public endpoints—such as those used to fetch product SKUs for frontend rendering—intentionally remain unauthenticated. This design choice presents a unique challenge: how to maintain both accessibility and protection against malicious usage patterns.

.NET Core provides built-in protections against fundamental threats like XSS, SQL injection, CSRF, and unsafe redirects. However, mitigating more sophisticated threats—such as denial-of-service (DoS), distributed denial-of-service (DDoS), bulk scraping, probing, and abusive rate-heavy requests—requires custom implementation. These can be enforced either at infrastructure (e.g., reverse proxy, CDN) or appplication levels.

Below, we explore three application-layer security mechanisms implemented via filters and middleware.

  1. Per-IP Request Throttling via Action Filter

This approach limits the number of requests an IP address can make within a defined window. Important to note: in shared NAT or proxy environments, multiple users may share a single IP, making this method less precise without additional hints (e.g., user-agent fingerprinting or token-based tracking).

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class IpRateLimitFilter : ActionFilterAttribute
{
    private const string CacheKeyPrefix = "IPRateLimit";

    public string EndpointId { get; }
    public int MaxRequests { get; }
    public int WindowSeconds { get; }

    private readonly IMemoryCache _cache;

    public IpRateLimitFilter(string endpointId, int maxRequests = 5, int windowSeconds = 10)
    {
        EndpointId = endpointId;
        MaxRequests = maxRequests;
        WindowSeconds = windowSeconds;
        _cache = new MemoryCache(new MemoryCacheOptions());
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var remoteIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
        var cacheKey = $"{CacheKeyPrefix}:{EndpointId}:{remoteIp}";

        if (_cache.TryGetValue(cacheKey, out int requestCount) && requestCount >= MaxRequests)
        {
            context.Result = new ContentResult
            {
                Content = "Too many requests. Please retry later.",
                StatusCode = StatusCodes.Status429TooManyRequests
            };
        }
        else
        {
            var options = new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(WindowSeconds)
            };
            _cache.Set(cacheKey, requestCount + 1, options);
        }
    }
}

Usage example:

[HttpGet]
[IpRateLimitFilter("ProductCatalog", maxRequests: 10, windowSeconds: 30)]
public IActionResult GetProducts()
{
    // Implementation
}

  1. Referer Header Validation

Validating the Referer (note the misspelling) header helps prevent unauthorized clients from calling API endpoints directly—particularly aiding in mitigating CSRF and unauthorized scraping attempts.

A simple domain protect list is used, configured via appsettings.json.

{
  "AllowedOrigins": [
    "https://example.com",
    "https://subdomain.example.co.uk:443",
    "http://localhost:8080"
  ]
}

public class OriginValidationFilter : ActionFilterAttribute
{
    private readonly string[] _allowedOrigins;

    public OriginValidationFilter(IConfiguration config)
    {
        _allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ?? Array.Empty<string>();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var request = context.HttpContext.Request;

        if (!string.IsNullOrWhiteSpace(request.Headers["Referer"].FirstOrDefault()))
        {
            var referer = request.Headers["Referer"].ToString();
            if (!Uri.TryCreate(referer, UriKind.Absolute, out var uri) ||
                !_allowedOrigins.Any(origin => Uri.TryCreate(origin, UriKind.Absolute, out var allowed) && 
                                               uri.Host.Equals(allowed.Host, StringComparison.OrdinalIgnoreCase) &&
                                               uri.Port == allowed.Port))
            {
                context.Result = new ContentResult
                {
                    StatusCode = StatusCodes.Status403Forbidden,
                    Content = "Request origin not permitted."
                };
            }
        }
    }
}

Applied to a controller action:

[HttpGet]
[OriginValidationFilter]
public IActionResult PublicData()
{
    // Implementation
}

  1. DDoS Mitigation via Middleware

This middleware monitors request frequency per IP and enforces a ban for a fixed period when abuse is detected.

public class DdosMitigationMiddleware
{
    private readonly RequestDelegate _next;
    private static readonly ConcurrentDictionary<string, RequestCounter> _requestCounts = new();
    private static readonly ConcurrentDictionary<string, DateTime> _bannedIps = new();

    private const int Threshold = 10;
    private const int BanDurationMs = 5 * 60 * 1000; // 5 minutes
    private const int DecayIntervalMs = 1000; // 1 second

    static DdosMitigationMiddleware()
    {
        var timer = new Timer(DecayRequests, null, DecayIntervalMs, DecayIntervalMs);
    }

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

    public async Task InvokeAsync(HttpContext context)
    {
        var ip = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";

        if (_bannedIps.ContainsKey(ip))
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            return;
        }

        var now = DateTime.UtcNow;

        var counter = _requestCounts.AddOrUpdate(
            ip,
            _ => new RequestCounter(now),
            (_, existing) => existing
        );

        lock (counter)
        {
            counter.AddRequest(now);
            if (counter.RequestsInWindow >= Threshold)
            {
                _bannedIps[ip] = now.AddMilliseconds(BanDurationMs);
                _requestCounts.TryRemove(ip, out _);
            }
        }

        await _next(context);
    }

    private static void DecayRequests(object state)
    {
        var now = DateTime.UtcNow;

        foreach (var entry in _requestCounts.ToList())
        {
            lock (entry.Value)
            {
                entry.Value.Refresh(now);
                if (entry.Value.RequestsInWindow == 0)
                {
                    _requestCounts.TryRemove(entry.Key, out _);
                }
            }
        }

        // Cleanup expired bans
        foreach (var entry in _bannedIps.ToList())
        {
            if (entry.Value <= now)
            {
                _bannedIps.TryRemove(entry.Key, out _);
            }
        }
    }

    private class RequestCounter
    {
        private readonly List<DateTime> _timestamps = new();
        private readonly object _lock = new();

        public RequestCounter(DateTime now) => AddRequest(now);

        public void AddRequest(DateTime now)
        {
            lock (_lock)
            {
                _timestamps.Add(now);
            }
        }

        public void Refresh(DateTime now)
        {
            lock (_lock)
            {
                _timestamps.RemoveAll(t => (now - t).TotalSeconds > 10);
            }
        }

        public int RequestsInWindow
        {
            get
            {
                lock (_lock)
                {
                    return _timestamps.Count;
                }
            }
        }
    }
}

Register in Program.cs or Startup.cs:

app.UseMiddleware<DdosMitigationMiddleware>();

Conclusion

Even public-facing APIs can become targets of abuse or attack when left exposed without basic safeguards. Implementing request rate limiting, origin validation, and DDoS countermeasures at the application layer—especially for unauthenticated endpoints—strengthens resilience and reduces potential blast radius.

Tags: .NET Core API Security Rate Limiting DDoS Mitigation ASP.NET Core Middleware

Posted on Tue, 21 Jul 2026 16:21:12 +0000 by jkatcher