I've got a website from the VS2013 SPA template (web api 2.2) which leverages ASP.NET Identity 2.1, and everything works really well. My controller methods look like this:
[Authorize]
public Api.Models.Widget Get(int widgetId)
{
var requestingUserId = Int32.Parse(Microsoft.AspNet.Identity.IdentityExtensions.GetUserId(User.Identity));
...
}
It works as expected:
But I now want to modify the application to prevent excessive API requests. I plan to check to see if that particular userID had made a certain number of requests within a certain timeframe. I'm looking for suggestions for the best place to do this.
I would like not to have to repeat this logic in every controller, and it seems like an action filter might be the best place. But as this will need to read the userid and I'm not sure if the order of filters is guaranteed, it may also make sense, if possible, to derive the filter which is already being called for authorization and to add my extra logic.
I'm wondering if anyone could give an example of doing something similar? It seems that instead of "authorize", it would be in a custom authentication filter, and I'm not sure how I would tie this together.
Thanks for any suggestions...
There are several filter options:
Authorization filter Makes security decisions about whether to execute an action method, such as performing authentication or validating properties of the request.
Example:
public class WebApiAuthorizeAttribute : AuthorizeAttribute
{
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
base.OnAuthorization(actionContext);
Guid userId = new Guid(HttpContext.Current.User.Identity.GetUserId());
// ...here your validation logic
}
}
Action filter Wraps the action method execution. This filter can perform additional processing, such as providing extra data to the action method, inspecting the return value, or canceling execution of the action method
To minimize the impact on your server, you can cache any http get request in the user browser for a predefined time, if the user request the same URL in that predefined time the response will be loaded from the browser cache instead of the server. As OutputCache Attribute is not available for Web API, you can use this Output caching in ASP.NET Web API post as alternative or you may implement your own Action filter attribute for Caching:
public class CacheFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// Gets or sets the cache duration in seconds. The default is 10 seconds.
/// </summary>
/// <value>The cache duration in seconds.</value>
public int Duration
{
get;
set;
}
public CacheFilterAttribute()
{
Duration = 10;
}
public override void OnActionExecuted(FilterExecutedContext filterContext)
{
if (Duration <= 0) return;
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
}
Additional Considerations
Once the user makes a call against your web api, you will have to add +1 to the counter and then use this counter to check massive calls with the same user in a timeframe. The problem here is where to store this counter.
If you store the counter in a RDBMS like SQL Server, every user call will perform a DB access. This could become a performance problem. This storage should be as light weight as possible. So using a NoSQL db might be a good approach.