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

Endpoints

This page describes the available mechanisms to secure your endpoints.

An endpoint in Play is either an Action or a WebSocket and Silhouette provides mechanisms to secure both of them. What all mechanisms share is the necessity to implement the Silhouette controller.

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

This controller provides the Silhouette Environment and it defines the Identity and the Authenticator the endpoints can handle.

Request Handlers

New in version 2.0

The base implementations to handle secured endpoints are encapsulated into request handlers which can execute an arbitrary block of code and must return a HandlerResult . This HandlerResult consists of a normal Play result and arbitrary additional data which can be transported out of these handlers.

There exists a SecuredRequestHandler which intercepts requests and checks if there is an authenticated user. If there is one, the execution continues and your code is invoked.

There is also a UserAwareRequestHandler that can be used for endpoints that need to know if there is a current user but can be executed even if there isn't one.

class Application(env: Environment[User, CookieAuthenticator])
  extends Silhouette[User, CookieAuthenticator] {
    
  /**
   * An example for a secured request handler.
   */
  def securedRequestHandler = Action.async { implicit request =>
    SecuredRequestHandler { securedRequest =>
      Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
    }.map {
      case HandlerResult(r, Some(user)) => Ok(Json.toJson(user.loginInfo))
      case HandlerResult(r, None) => Unauthorized
    }
  }

  /**
   * An example for an user aware request handler.
   */
  def userAwareRequestHandler = Action.async { implicit request =>
    UserAwareRequestHandler { userAwareRequest =>
      Future.successful(HandlerResult(Ok, userAwareRequest.identity))
    }.map {
      case HandlerResult(r, Some(user)) => Ok(Json.toJson(user.loginInfo))
      case HandlerResult(r, None) => Unauthorized
    }
  }
}

📘

Note

For unauthenticated users you can implement a global or local fallback handler.

Actions

Silhouette provides a replacement for Play’s built in Action class named SecuredAction which is based on the SecuredRequestHandler.

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

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

There is also a UserAwareAction which is based on the UserAwareRequestHandler.

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

  /**
   * Renders the index page.
   *
   * @returns The result to send to the client.
   */
  def index = UserAwareAction { implicit request =>
    val userName = request.identity match {
      case Some(identity) => identity.fullName
      case None => "Guest"
    }
    Ok("Hello %s".format(userName))
  }
}

WebSockets

New in version 2.0

With Silhouette it'a also possible to secure WebSockets with the help of the SecuredRequestHandler or the UserAwareRequestHandler. Please take a look on the following examples to see how this can be implemented.

WebSockets with actors

object MyWebSocketActor {
  def props(user: User)(out: ActorRef) = Props(new MyWebSocketActor(user, out))
}

class MyWebSocketActor(user: User, out: ActorRef) extends Actor {
  def receive = {
    case msg: String =>
      out ! (s"Hi ${user.name}, I received your message: " + msg)
  }
}

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

  def socket = WebSocket.tryAcceptWithActor[String, String] { request =>
    implicit val req = Request(request, AnyContentAsEmpty)
    SecuredRequestHandler { securedRequest =>
      Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
    }.map {
      case HandlerResult(r, Some(user)) => Right(MyWebSocketActor.props(user) _)
      case HandlerResult(r, None) => Left(r)
    }
  }
}

WebSockets with iteratees

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

  def socket = WebSocket.tryAccept[JsValue] { request =>
    implicit val req = Request(request, AnyContentAsEmpty)
    SecuredRequestHandler { securedRequest =>  
      Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
    }.map {
      case HandlerResult(_, Some(_)) => Right((ws.in, ws.out))
      case HandlerResult(r, None)    => Left(r)
    }
  }
}

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 SecuredSettings trait into your Global object. This trait provides a method called onNotAuthenticated. If you implement this method, then every time a user calls a restricted endpoint, the result specified in the global fallback method will be returned.

object Global extends GlobalSettings with SecuredSettings {

  /**
   * Called when a user is not authenticated.
   *
   * As defined by RFC 2616, the status code of the response should be 401 Unauthorized.
   *
   * @param request The request header.
   * @param lang The currently selected language.
   * @return The result to send to the client.
   */
  override def onNotAuthenticated(request: RequestHeader, lang: Lang) = {
    Some(Future.successful(Unauthorized("No access")))
  }
}

Local Fallback

Every controller which is derived from the Silhouette base controller has a method called onNotAuthenticated. If you override these method, then you can return a not-authenticated 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 not authenticated.
   *
   * As defined by RFC 2616, the status code of the response should be 401 Unauthorized.
   *
   * @param request The request header.
   * @return The result to send to the client.
   */
  override def onNotAuthenticated(request: RequestHeader): Option[Future[Result]] = {
    Some(Future.successful(Unauthorized("No access")))
  }

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

📘

Note

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

Handle Ajax requests

Applications that accept both Ajax and normal requests should likely provide a JSON result to the first and a different result to others. There are two different approaches to achieve this. The first approach uses a non-standard HTTP request header. The Play application can check for this header and respond with a suitable result. The second approach uses Content negotiation to serve different versions of a document based on the ACCEPT request header.

Non-standard header

The example below uses a non-standard HTTP request header inside a
secured action and inside a fallback method for unauthenticated users.

The JavaScript part with JQuery

$.ajax({
    headers: { 'IsAjax': 'true' },
    ...
});

The Play part with a local fallback method for unauthenticated users

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

  /**
   * Implement this to return a result when the user is not authenticated.
   *
   * As defined by RFC 2616, the status code of the response should be 401 Unauthorized.
   *
   * @param request The request header.
   * @return The result to send to the client.
   */
  override def onNotAuthenticated(request: RequestHeader): Option[Future[Result]] = {
    val result = request.headers.get("IsAjax") match {
      case Some("true") => Json.obj("result" -> "No access")
      case _ => "No access"
    }

    Some(Future.successful(Unauthorized(result)))
  }

  /**
   * Renders the index page.
   *
   * @returns The result to send to the client.
   */
  def index = SecuredAction { implicit request =>
    val result = request.headers.get("IsAjax") match {
      case Some("true") => Json.obj("identity" -> request.identity)
      case _ => views.html.index(request.identity)
    }

    Ok(result)
  }
}

Content negotiation

By default Silhouette supports content negotiation for the most common media types: text/plain, text/html, application/json and application/xml. So if no local or global fallback methods are implemented, Silhouette responds with the appropriate response based on the ACCEPT header defined by the user agent. The response format will default to plain text in case the request does not match one of the known media types. The example below uses content negotiation inside a secured action and inside a fallback method for unauthenticated users.

The JavaScript part with JQuery

$.ajax({
    headers: {
        'Accept': 'application/json; charset=utf-8',
        'Content-Type': 'application/json; charset=utf-8'
    },
    ...
})

The Play part with a local fallback method for unauthenticated users

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

  /**
   * Implement this to return a result when the user is not authenticated.
   *
   * As defined by RFC 2616, the status code of the response should be 401 Unauthorized.
   *
   * @param request The request header.
   * @return The result to send to the client.
   */
  override def onNotAuthenticated(request: RequestHeader): Option[Future[Result]] = {
    val result = render {
      case Accepts.Json() => Json.obj("result" -> "No access")
      case Accepts.Html() => "No access"
    }

    Some(Future.successful(Unauthorized(result)))
  }

  /**
   * Renders the index page.
   *
   * @returns The result to send to the client.
   */
  def index = SecuredAction { implicit request =>
    val result = render {
      case Accepts.Json() => Json.obj("identity" -> request.identity)
      case Accepts.Html() => views.html.index(request.identity)
    }
    Ok(result)
  }
}