These docs are for v3.0. Click to read the latest docs for v7.0.

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.
   * @param messages The messages for the current language.
   * @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], messages: Messages): Future[Boolean]
}

This is a sample implementation that only grants access to users that logged in using a given provider:

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

Here’s how you would use it:

def myAction = SecuredAction(WithProvider("twitter")) { implicit request =>
  // do something here
}

For unauthorized users you can implement a global or local fallback handler similar to the fallback handlers for unauthenticated users.

Logic Operator

New in version 2.0

You can use the logical !, && and || operators to create logical expressions with your Authorization instances.

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

Fallback handler

If the access to a secured endpoint will be denied then it's possible to provide a fallback handler to handle the incoming request and return an appropriate result.

Global Fallback

You can mix the SecuredErrorHandler trait into a Play error handler implementation. This trait provides a method called onNotAuthorized. If you implement this method, then every time a user calls an endpoint on which he isn't authorized, the result specified in the global fallback method will be returned.

import javax.inject.Inject

import com.mohiva.play.silhouette.api.SecuredErrorHandler
import controllers.routes
import play.api.http.DefaultHttpErrorHandler
import play.api.i18n.Messages
import play.api.mvc.Results._
import play.api.mvc.{ Result, RequestHeader }
import play.api.routing.Router
import play.api.{ OptionalSourceMapper, Configuration }

import scala.concurrent.Future

class ErrorHandler @Inject() (
  env: Environment,
  config: Configuration,
  sourceMapper: OptionalSourceMapper,
  router: Provider[Router])
  extends DefaultHttpErrorHandler(env, config, sourceMapper, router)
  with SecuredErrorHandler {

  /**
   * Called when a user is authenticated but not authorized.
   *
   * As defined by RFC 2616, the status code of the response should be 403 Forbidden.
   *
   * @param request The request header.
   * @param messages The messages for the current language.
   * @return The result to send to the client.
   */
  override def onNotAuthorized(request: RequestHeader, messages: Messages) = {
    Some(Future.successful(Forbidden("Not authorized")))
  }
}

Local Fallback

Every controller which is derived from Silhouette base controller has a method called onNotAuthorized. If you override these method, then you can return a not-authorized result similar to the global fallback but only for this specific controller. The local fallback has precedence over the global fallback.

class Application(env: Environment[User, CookieAuthenticator])
  extends Silhouette[User, CookieAuthenticator] {

  /**
   * Implement this to return a result when the user is authenticated but not authorized.
   *
   * As defined by RFC 2616, the status code of the response should be 403 Forbidden.
   *
   * @param request The request header.
   * @return The result to send to the client.
   */
  override def onNotAuthorized(request: RequestHeader): Option[Future[Result]] = {
    Some(Future.successful(Forbidden("Not authorized")))
  }

  /**
   * Renders the index page.
   *
   * @returns The result to send to the client.
   */
  def index = SecuredAction(WithProvider("twitter")) { implicit request =>
    Ok(views.html.index(request.identity))
  }
}

📘

Note

If you don’t implement one of the both fallback methods, a 403 response with a simple message will be displayed to the user.