Play framework security with Silhouette


Silhouette is a security library for Play framework. Its basically a core only fork of Secure Social, which is another security library for play framework. Secure Social is a great framework for lots of user but my experience with secure social is not that good, nor with some of my friends and colleagues who use Play framework in their application. It’s been thrown out from the projects we worked on. However I am not going to talk about secure social here, technologies are like that only, it works for some people and doesn’t work for some other people, and for whom it do not work they find or build some alternatives. Silhouette is born because of securesocial’s problems, mostly because of its tight coupling. Silhouette is a loosely coupled security library. It gives users the flexibility to inject their own implementation when things don’t seem work with the given implementation.

We can use the library in the play controllers by extending the Silhouette trait.

class ApplicationController @Inject() (implicit val env: Environment[User, JWTAuthenticator])
extends Silhouette[User, JWTAuthenticator] {}

In this code an implementation of the environment is given implicitly and all the requirements for the environment is given through Guice dependency injection. At this point it looks a bit confusing, so lets see how the stuffs inside Silhouette works.

Inside Silhouette the codes are divided in two categories api and impl. Here api is the main engine of the library. All the work flow is handled here and it’s mostly consist of traits. And there is the impl part which gives us the default implementation of things we generally required in our applications e.g. implementation for the providers – OAuth1, OAuth2, OpenId , some service implementations, some authenticator implementations etc. The trait Silhouette is the main trait in this library it handles all the function calling and that sort of stuffs of the library. It needs an environment to handle all the functionality. The environment holds all the required services to drive the library according to the user’s perspective. Below is a diagram which tells shows what are the services the environment holds.

Silhouette_strcture

So here we can see the environment holds IdentityService, AuthenticatorService, RequestProvider s and EventBus. Out of these services IdentityService needs to be implemented by the user, other services however are present in the impl package, i.e. we can use their implementation of give our own implementation.

In order to implement identity service we have to extend the trait IdentityService. The only method the IdentityService holds is the retrieve method which takes a parameter LoginInfo(LoginInfo is a case class which holds two parameters providerId, providerKey as string). However we can add our own methods here as well. Below is implementation of IdentityService.

case class User(
userID: UUID,
loginInfo: LoginInfo,
firstName: Option[String],
lastName: Option[String],
fullName: Option[String],
email: Option[String],
avatarURL: Option[String],
userType: UserTypes.Value) extends Identity

trait UserService extends IdentityService[User] {
def save(user: User): Future[User]
}

class UserServiceImpl @Inject() (userDAO: UserDAO) extends UserService {
def retrieve(loginInfo: LoginInfo): Future[Option[User]] = userDAO.find(loginInfo)
def save(user: User) = userDAO.save(user)
}

Here we have implemented User by extending trait Identity. Then we have given the type of IdentityService as User and extends the UserService where we have added one more method save which takes a user parameter. And finally we have implemented it in the UserServiceImpl. Here the userDao implementation is being injected by guice, it has all the implementation of the database interaction.

Then there is the AuthenticatorService which basically deals with authentication, it helps to identify if the user’s session has expired or not. There are two traits to deal with the authentication Authenticator and StorableAuthenticator. The Authenticator trait consists of type value, which is used for identifying, with which type the authenticator will be serialized, then it has loginInfo: LoginInfo, I have mentioned about LoginInfo before, then it has isValid:Boolean which indicates if the returning customer’s session has expired or not. StorableAuthenticator extends Authenticator and adds another method called id to reference the authenticator in the backing store. AuthenticatorService trait has all the operation for Authenticator such as create, retrieve, init, embed, touch, update, renew and discard. In the impl package we can find the implementation for both these two things i.e. Authenticator and AuthenticatorService. Again it’s up to the library user to use what’s the best option for their application, either use the given implementation or implement according to your preference.

The Environment holds collection of Providers. Provider basically is a service to identify the user in the applicaiton. Silhouette is providing 14 different implementation for different providers including facebook, linkedin, github, twitter, yahoo, google etc. Its up to the user to use the implemented providers or implement their own providers by implementing the traits Provider and RequestProvider. We can embed more than one provider in our application and that is why environment takes the providers as collection…Seq[Provider].
The EventBus basically handles the events that occured in the application. We can give our own implementation of events by implementing the trait SilhouetteEvent. Its basically based on Akka’s event bus.

That was all about the environment that is required for the Silhouette to work on. Once the environment is ready we are good to go to secure our endpoints. While developing the application we might want some actions to be accessed by authenticated users only, some actions to be used by any user, or some actions need some authorization for it. All these stuffs are implemented in the Silhouette trait. We can use SecuredAction to secure an action which needs to be performed by authenticated users only e.g.

def user = SecuredAction.async { implicit request =>;
Future.successful(Ok(Json.toJson(request.identity)))
}

Or when we want some action to be authorized we can do this

def adminActions = SecuredAction(AdminRights).async{
implicit request =>;
Future.successful(Ok(Json.toJson(request.identity)))
}

object AdminRights extends Authorization[User]{
override def isAuthorized(identity: User)(implicit request: RequestHeader, lang: Lang): Boolean = {
identity.userType == UserTypes.Admin
}
}

object UserTypes extends Enumeration{
val Admin, NormalUser = Value
}

Here the SecuredAction is taking AdminRights as parameter which is implemented by extending Authroization trait. It uses UserTypes which is an enum and has values Admin and Normal User. So when we pass AdminRights to the SecuredAction that simply means only the admin can access that action. I am just giving a simple implementation to it, if required we can put more stuffs in the isAuthorized method.

UserAwareAction basically does not need any authentication or authorization for it, but if the user is authenticated or authorized it follows the other things, like keep the user session alive or stuffs like that. We can use it simply like below

def view(template: String) = UserAwareAction { implicit request =>;
template match {
case "home" =>; Ok(views.html.home())
case "signUp" =>; Ok(views.html.signUp())
case "signIn" =>; Ok(views.html.signIn())
case "navigation" =>; Ok(views.html.navigation.render())
case _ =>; NotFound
}
}

To summarize the topic let’s divide the user actions in two parts Signin/Signup and Any Other actions. Singin/Signup does not really need any authentication or authorization however other actions will need authentication or authorization to perform.

Silhouette_workflow (3)

Silhouette_workflow (2)

The diagrams that I am using here are just to visualize the flow, more actions are included in the workflow in real. Users can directly use signin throw the OAuth1 or OAuth2 or similar providers, their signup process is generally included in this part. So whenever the Signin/Signup happens Identity service is used to save or retrieve the identity then the Authentication service is called to create the authenticator and initiates the authentication process e.g. sending some token in the cookies, set timeout option etc. Publishing of the Signin/Signup event occurs here. Then finally the code block gets called and the result is send back to the result. Result could be the json or the play template here.

However in case of any other actions, the flow becomes a bit different. For these actions some sort of identities are sent from the user end. Then the Authentication service validates this identification, once its get validated the identity service is called to retrieve the identity and authorization action gets performed if there are any. Once its get authenticated or authorized the code block gets invoked. Once the code block gets invoked the result is sent to the user. So thats all about it. More about Silhouette could be find here.

Note: Most of the code snippets used here are taken form here.

Advertisements
This entry was posted in Play Framework, Scala, Web and tagged , , . Bookmark the permalink.

7 Responses to Play framework security with Silhouette

  1. kd says:

    really nice article!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s