Rate limiting

Silhouette can be combined with play-guard to provide rate-limiting based on the needs of your specific application (note: Silhouette does not hold any dependency to play-guard). Common use cases are things such as throttling by IP, as well as throttling a computationally expensive endpoint for an authenticated user not to spam refresh.

The following is an example for building a rate limiting action for a user that is already logged In:

/**
 * A Limiter for user logic.
 */
object UserLimiter {

  /**
   * A Rate limiter Function for.
   *
   * @param rateLimiter The rate limiter implementation.
   * @param reject The function to apply on reject.
   * @param requestKeyExtractor The Request Parameter we want to filter from.
   * @param actorSystem The implicit Akka Actor system.
   * @tparam K the key by which to identify the user.
   */
  def apply[T <: Env, R[_] <: SecuredRequest[T, _], K](rateLimiter: RateLimiter)(
    reject: R[_] => Result, requestKeyExtractor: R[_] => K
  )(
    implicit 
    actorSystem: ActorSystem
  ): RateLimitActionFilter[R] with ActionFunction[R, R] = {
    new RateLimitActionFilter[R](rateLimiter)(reject, requestKeyExtractor) with ActionFunction[R, R]
  }
}

We could then define our own implementation of the filter as follows:

type Secured[B] = SecuredRequest[MyEnv, B]

/**
   * A default user filter implementation.
   *
   * @param ac The Akka Actor System implicitly provided.
   */
  def defaultUserFilter(implicit ac: ActorSystem): RateLimitActionFilter[Secured] with ActionFunction[Secured, Secured] = {
    (UserLimiter.apply[MyEnv, Secured, UUID](new RateLimiter(10, 1f / 10, "Default User Limiter"))
      (_ => Results.TooManyRequests("You've been refreshing too much. Please try again in 10 seconds"), r => r.identity.userID))
  }

We can then compose actions for some arbitrary, dependency injected controller as such:

class MyController @Inject()(silhouette: Silhouette[MyEnv])(implicit ac: ActorSystem) extends Controller {

  private val defaultUserFilter = UserLimiter.defaultUserFilter
  
  def myAction: Action[AnyContent] = (silhouette.SecuredAction andThen defaultUserFilter).async {
    implicit request =>
      
    //..Your custom logic here
  }
}

We can also leverage some of the built-in limiters within play-guard. In the following example, we will use the built-in HttpErrorLimitAction to throttle the number of logins a user from a particular IP can make before they are throttled from logging in entirely within some controller:

/**
 * A rate limiter based on failed requests.
 */
private val httpErrorRateLimited: ActionBuilder[Request] =
  HttpErrorRateLimitAction(new RateLimiter(2, 1f / 10, "test failure rate limit")) { _ => Results.TooManyRequests("failure rate exceeded") }
    
def submit = (httpErrorRateLimited andThen silhouette.UnsecuredAction).async { implicit request => 
  ...
}