Authentication Filter Function in Play! Framework

Initiating a session and keeping a session for each user can be problematic, sometimes. Especially, if you are doing this manually. However, general idea is simple. You need to put each coming request to a filter which checks whether request has a valid session or not. In this article, I will simply explain how I do this filtering on Scala-Play.

Play Framework has GlobalSettings class which has couple of functions that you can monitor any incoming request. However, it is not good idea to use GlobalSettings for this purpose. Actually, Play is trying to remove GlobalSettings completely. You can read more about this from here.

The better solution is to use capabilities of Scala as a Functional Language. Let’s go step by step.

def changeUserRole() = Authenticated(parse.json) { (request, email, sessionId) =>
request.body.validate[ChangeUserRoleRequest].fold(
valid = {changeUserRoleRequest =>
userService.changeUserRole(changeUserRoleRequest).map { result =>
Ok(ResponseBase.response(result))
}
},
invalid = {e => Logger.error(s"[changeUserRole] error: $e")
Future.successful(Ok(ResponseBase.error(Messages.get("json_field_error")).toResultJson))
}
)
}

This is a method inside a Controller which require authentication. As you see in this code, we add Authenticated(parse.json) instead of Action.async(parse.json). Authenticated is a method which takes BodyParser as input and a method which return Future[Result] and takes 3 parameters. Authenticated method returns Action[T].

Validation(request.body.validate) is another great feature of Play. It is automatically maps an incoming request to Scala object and validate. Validation means that decide whether incoming request has required parameters. This might be a topic of another post.

For this Authenticated method I wrote a simple trait which is used by the Controller.

trait AuthenticationHelper {
self:Controller with SessionServiceComponent =>
def Authenticated[T](bp: BodyParser[T])(f: (Request[T], String, String) => Future[Result]): Action[T] = Action.async(bp) { request =>
val emailOpt = request.session.get(Constants.EMAIL)
val sessionIdOpt = request.session.get(Constants.SESSION_ID)
(emailOpt, sessionIdOpt) match {
case (Some(email), Some(sessionId)) =>
sessionService.checkValidityAndUpdateIfValid(email, sessionId).flatMap { sessionResult =>
if(sessionResult) {
f(request, email, sessionId)
} else {
Future.successful(Redirect(routes.UserController.login()))
}
}
case _ =>
Future.successful(Redirect(routes.UserController.login()))
}
}
def Authenticated(f: (Request[AnyContent], String, String) => Future[Result]):Action[AnyContent] = Authenticated(parse.anyContent)(f)
def Authenticated(f: => Future[Result]):Action[AnyContent] = Authenticated(parse.anyContent)((_, _, _) => f)
}

There are three methods which have the same name. Last two are written just for easily using the function inside Controller. Both of them are using the first one. I want to explain first one. It look for session values first. I put email of user and sessionID (which is randomly generated string) as session cookie while sending response for login. If the method is secured by this function, I am expecting to receive this email and sessionID value from the request.

This mechanism automatically works for browsers. For Android, the same HttpClient should be used for login and other requests. If you are using Volley, Retrofit etc., They have their own specification for enabling session cookies, which are generally easy to use.

I have a session service which goes to database and look for session registered for this email. If the service find a valid session, it updates the record so that session will be valid for another 15 minutes (each session is created for 15 minutes). If session is valid, I return result of the function that I took as parameter (result of changeUserRole method) first otherwise redirect user to login page.

After 15 minutes, user will not be able to use this session. If this is the case, I simply redirect user to login page. If it is REST api, just send response which contains an invalid session error, so that your client side app can redirect user to login.

It is possible to implement user-role based version. Simply adding a check which decide if the owner of session is allowed to use the method.

As a result, It is enough to put Authenticated to your function inside controller. It is also possible to use email inside the controller functions, which is very nice and sufficient for many request.

Do not forgot to import play.api.libs.concurrent.Execution.Implicits.defaultContext in your trait.

Suggestions and improvements are more than welcome!