20

I'm building an ASP.NET MVC site where I want to limit how often authenticated users can use some functions of the site.

Although I understand how rate-limiting works fundamentally, I can't visualize how to implement it programatically without creating a major code smell.

Can you point me towards a simple yet powerful solution for approaching such a problem, with C# sample code?

If it matters, all of these functions are currently expressed as Actions that only accept HTTP POST. I may eventually want to implement rate-limiting for HTTP GET functions as well, so I'm looking for a solution that works for all such circumstances.

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
Maxim Zaslavsky
  • 17,787
  • 30
  • 107
  • 173
  • Here is another complete tutorial how to do it, which allows more flexible intervals: https://www.shieldui.com/blogs/rate-limiting-in-asp-net-mvc – Vladimir Georgiev Jan 05 '16 at 12:44
  • Possible duplicate: https://stackoverflow.com/questions/33969/best-way-to-implement-request-throttling-in-asp-net-mvc – gio Mar 10 '22 at 07:54

3 Answers3

33

If you are using IIS 7 you could take a look at the Dynamic IP Restrictions Extension. Another possibility is to implement this as an action filter:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RateLimitAttribute : ActionFilterAttribute
{
    public int Seconds { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Using the IP Address here as part of the key but you could modify
        // and use the username if you are going to limit only authenticated users
        // filterContext.HttpContext.User.Identity.Name
        var key = string.Format("{0}-{1}-{2}",
            filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
            filterContext.ActionDescriptor.ActionName,
            filterContext.HttpContext.Request.UserHostAddress
        );
        var allowExecute = false;

        if (HttpRuntime.Cache[key] == null)
        {
            HttpRuntime.Cache.Add(key,
                true,
                null,
                DateTime.Now.AddSeconds(Seconds),
                Cache.NoSlidingExpiration,
                CacheItemPriority.Low,
                null);
            allowExecute = true;
        }

        if (!allowExecute)
        {
            filterContext.Result = new ContentResult
            {
                Content = string.Format("You can call this every {0} seconds", Seconds)
            };
            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
        }
    }
}

And then decorate the action that needs to be limited:

[RateLimit(Seconds = 10)]
public ActionResult Index()
{
    return View();
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    How would you extend this to use `429 - Too Many Requests` as per [RFC 6586](http://tools.ietf.org/html/rfc6585) – Stuart Blackler Jul 11 '14 at 13:56
  • 2
    @StuartBlacker I would assume you would edit the following line: `filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;` from Conflict to a 429 error. – NicholasFolk Feb 19 '17 at 21:23
  • Great solution here. I love it. I'm going to see if I can do sth (something) similar. Maybe sth with an LRU or sliding window timeout. – Pangamma May 21 '18 at 17:41
  • filterContext.Result = new HttpStatusCodeResult(429, "too many requests"); – Mark Good Dec 14 '21 at 13:02
  • 1
    We tried this and encountered problems with users having the same IP address, such as multiple users connecting from the same large organization (Ford, for example.) There are other attributes of the request that we could use, but all of the ones I have seen so far are "spoofable" (user-agent, SessionID, request headers, etc.) – Mark Good Oct 12 '22 at 14:21
  • The IP issue also happens if the server/site is behind some firewall. – Matt Jan 25 '23 at 17:57
5

Have a look at Jarrod's answer on how they do this on SO.

StackOverflow MVC Throttling

Some example code as well as explanation on how it works.

Community
  • 1
  • 1
Tommy
  • 39,592
  • 10
  • 90
  • 121
  • I have a doubt , What if does `DOS` attack hits the web server, then how come this code level protection will help ? Doesn't we need solution on web server or machine level ? – Shaiju T Dec 08 '17 at 09:46
  • 2
    @stom - If the `DOS` attack is making to your webservers, you have already lost a decent amount of the battle. Even if the web server simply drops the packets, the attack is till tying up bandwidth and server resources to listen, inspect and drop the packet(s). Rate Limiting is more to prevent abuse from a single user or bots, not necessarily a total defense against `DOS` attacks though it may help a bit in that you're not executing DB calls/functions for every request like you would with no rate throttling in place. – Tommy Dec 08 '17 at 14:38
2

.Net 6

Check it out:

Nuget: https://www.nuget.org/packages/DotNetRateLimiter/

It is simple:

[HttpGet("")]
[RateLimit(PeriodInSec = 60, Limit = 3)]
public IEnumerable<WeatherForecast> Get()
{
    ....
}

And even you can control request by route or query parameters:

[HttpGet("by-query/{id}")]
[RateLimit(PeriodInSec = 60, Limit = 3, RouteParams = "id", QueryParams = "name,family")]
public IEnumerable<WeatherForecast> Get(int id, string name, [FromQuery] List<string> family)
{
    ....
}
sa-es-ir
  • 3,722
  • 2
  • 13
  • 31