Authorization

Silhouette provides a way to add authorization logic to your secured endpoints. This is done by implementing an Authorization object that is passed to all SecuredRequestHandler and SecuredAction as a parameter.

After checking if a user is authenticated the Authorization instance is used to verify whether the execution should be allowed or not.

/**
 * A trait to define Authorization objects that let you hook
 * an authorization implementation in secured endpoints.
 *
 * @tparam I The type of the identity.
 * @tparam A The type of the authenticator.
 */
trait Authorization[I <: Identity, A <: Authenticator] {

  /**
   * Checks whether the user is authorized to execute an endpoint or not.
   *
   * @param identity The current identity instance.
   * @param authenticator The current authenticator instance.
   * @param request The current request.
   * @tparam B The type of the request body.
   * @return True if the user is authorized, false otherwise.
   */
  def isAuthorized[B](identity: I, authenticator: A)(
    implicit request: Request[B]): Future[Boolean]
}

Below is a sample implementation that only grants access to users that logged in using a given provider. This example comes in two variants. The first has a defined authenticator type which is only applicable to actions which are defined with the same authenticator type. The second example uses a generic authenticator type which is applicable to any kind of action, regardless of the authenticator type defined for this action. The second case can also be used with a generic identity and it's useful if the authorization depends either on the identity or the authenticator.

case class WithProvider(provider: String) extends Authorization[User, CookieAuthenticator] {
  
  def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(
    implicit request: Request[B]) = {
    
    Future.successful(user.loginInfo.providerID == provider)
  }
}
case class WithProvider[A <: Authenticator](provider: String) extends Authorization[User, A] {
  
  def isAuthorized[B](user: User, authenticator: A)(
    implicit request: Request[B]) = {
    
    Future.successful(user.loginInfo.providerID == provider)
  }
}

Here’s how you would use it:

class Application(silhouette: Silhouette[DefaultEnv]) extends Controller {

  def myAction = silhouette.SecuredAction(WithProvider("twitter")) { implicit request =>
    // do something here
  }
}
class Application(silhouette: Silhouette[DefaultEnv]) extends Controller {

  def myAction = silhouette.SecuredAction(WithProvider[DefaultEnv#A]("twitter")) { implicit request =>
    // do something here
  }
}

📘

Info

Errors for not authorized users can be caught with the global or local error handlers.

Logic Operator

You can use the logical !, && and || operators to create logical expressions with your Authorization instances. You also need to have an implicit ExecutionContext in scope.

def myAction = silhouette.SecuredAction(!WithProvider("twitter")) { implicit request =>
  // do something here
}
def myAction = silhouette.SecuredAction(WithProvider("twitter") && WithProvider("facebook")) { 
  implicit request => // do something here
}
def myAction = silhouette.SecuredAction(WithProvider("twitter") || WithProvider("facebook")) { 
  implicit request => // do something here
}

What’s Next