Authenticating Users in the Play Framework
Let me introduce myself. I’m Daniel (aka QuantumBear), Cofounder of TunnelBear. My blog posts will be technical and focus on coding and development of TunnelBear apps and services.
At TunnelBear we are big fans of the Play Framework and Scala. So in my first post, I’m kicking things off with a little explanation of how to get started with user authentication in the Play framework. Along the way we will see a few common concepts used in Scala code such as functions as first class objects, case classes, pattern matching, options, implicits and traits.
The finished code for this project is available here.
First a look at what a standard API endpoint, or Action, looks like in the Play Framework v2.x:
def hello = Action { Ok("hello") }
To construct an Action we pass it some code that returns a Result (in this case, the OK result). Note that the argument to create an Action is actually a function itself. In this case a function that takes no arguments (sometimes called a code block) and that returns a Result.
Being able to pass functions as parameters like this is a key difference between Java and Scala and we will be making good use of it going forward.
For authenticated APIs, we found ourselves repeatedly checking usernames and passwords in the same way throughout the codebase. For example, if a user is defined like this:
`case class User(username:String, password:String) {
def checkPassword(password: String): Boolean = this.password == password }
//User lookup
object User {
def find(username: String):Option[User] = users.filter(_.username == username).headOption
}`
Then the authenticated actions might look like this:
def helloUser(username:String, password:String) = Action { val user:Option[User] = User.find(username).filter(_.checkPassword(password)) user match { case Some(user) => Ok(s"hello ${user.username}") case None => Forbidden("I don’t know you") } }
Wouldn’t it be nice if we could write code that acted directly on a User object without explicitly handling authentication every time? Perhaps actions like this:
def helloUser(username:String, password:String) = AuthenticateMe(username:String, password:String) { user:User => Ok(s"hello ${user.username}") }
How do we go about constructing AuthenticateMe?
Well it’s a method that needs to return an Action, and its argument is a username, password and a User => Result function. We just need to handle the two cases where authentication succeeds and fails, returning a Forbidden result in the failure case and executing argument function otherwise:
def AuthenticateMe(username:String, password:String)(f: User => Result) = Action { val user:Option[User] = User.find(username).filter(_.checkPassword(password)) user match { case Some(user) => f(user) case None => Forbidden("I don’t know you") } }
All that has been done here is factor out the common authentication and error handling code and make the dynamic part (i.e. the code that gives us a Result from a User) an argument.
AuthenticateMe is making use of Options and Pattern Matching, two useful Scala constructs described nicely in The Neophyte’s Guide to Scala
Taking things further:
Passing the username and password around is a little clunky, and what about handling other forms of authentication like cookies?
Let’s aim for something like this:
def helloUser = AuthenticateMe { user => Ok(s"hello ${user.username}") }
But how are the username, password or cookie to be accessed now?
First, let’s dig a bit deeper into actions:
An Action actually takes a function of type Request => Result as an argument, where the Request holds information about the incoming http request such as it’s parameters, headers, cookies etc. So you can think of an Action as telling the Play Framework how to convert an incoming http request to a response, which it can then render.
In the above examples we didn’t need to use the Request object explicitly and so used an Action constructor which ignores the Request. Actions can also be written with the request like this:
def hello = Action { request => Ok("hello") }
In order to get at the authentication details this time, the request parameter is needed.
First a few helpers that extract useful information from requests:
`object AuthUtils {
def parseUserFromCookie(implicit request: RequestHeader) = request.session.get("username").flatMap(username => User.find(username))
def parseUserFromQueryString(implicit request:RequestHeader) = {
val query = request.queryString.map { case (k, v) => k -> v.mkString }
val username = query get ("username")
val password = query get ("password")
(username, password) match {
case (Some(u), Some(p)) => User.find(u).filter(user => user.checkPassword(p))
case _ => None
}
}
def parseUserFromRequest(implicit request:RequestHeader):Option[User] = {
parseUserFromQueryString orElse parseUserFromCookie
}
}`
parseUserFromQueryString looks for “username” and “password” http parameters in the request, and if found attempts to return the authorised user. If no user is found there, the fallback is to look for a signed cookie containing the username.
Note the use of the implicit keyword for the request parameter.
If a parameter to a function is marked as implicit, the parameter does not need to be explicitly passed in. For this to work, you have to mark a suitable value as implicit somewhere in the scope of the function.
This can clean up code where the same parameter is passed to multiple functions.
Here is the improved version of our authentication wrapper:
def AuthenticateMe(f: User => Result) = Action { implicit request => val user = AuthUtils.parseUserFromRequest user match { case Some(user) => f(user) case None => Forbidden("I don’t know you") } }
Extending:
There are times when you need finer control over what kind of users can access an api.
Let’s extend our users so they now have a “premium” status and also a balance:
case class User(username:String, password:String, isPremium:Boolean, balance:Int) { def checkPassword(password: String): Boolean = this.password == password }
We might like to restrict an API to only those users who meet specific conditions. If we can define some traits that can be mixed in with a Controller, we can tell at a glance which users can access which API.
For example, for an API which only “premium users with a balance of at least 8” can access, constructing traits like PremiumUsersOnly and BalanceCheck might be handy. The aim is this:
`object PaidApplication extends Controller with Authentication with PremiumUsersOnly with BalanceCheck {
//set required balance here
override def getRequiredBalance = 8
//only premium user’s with balance of at least 8 should be allowed through
def helloUser = AuthenticateMe {
user => Ok(s"hello ${user.username}")
}
}`
Conditions:
Condition is a new type we define which is simply shorthand for “a function that takes a User and returns either a pass, or a fail”. The Either type will suit us well as a return type, being either a Right on success or a Left on failure. Either is like an extended Option that can not only signal a failure, but also hold information about the failure:
`object Conditions {
type Condition = (User => Either[String, Unit])
val isPremiumUser:Condition = {
user => if(user.isPremium)
Right()
else
Left("User must be premium")
}
def balanceGreaterThan(required:Int):Condition = {
user => if(user.balance > required)
Right()
else
Left(s"User balance must be > $required")
}
}`
AuthenticateMe now has to check not only the username and password as before, but also additional conditions on the user. Here are the additions neededin the Authentication trait:
`trait Authentication {
self:Controller =>
//additional conditions
var accessConditions: List[Conditions.Condition] = List.empty
def AuthenticateMe(f: User => Result) = Action { implicit request =>
val user = AuthUtils.parseUserFromRequest
if(user.isEmpty)
Forbidden("Invalid username or password")
else {
//username and password check have been passed.
//collectFirst will return the first condition in the Left state (i.e. error state)
accessConditions.map(condition => condition(user.get)).collectFirst[String]{case Left(error) => error}
match {
case Some(error) => Forbidden(s"Conditions not met: $error")
case _ => f(user.get)
}
}
}
}`
The Authentication trait holds a list of conditions which are tested after authentication is complete. If any of these Conditions fail, then we get the error message of the first one and display it to the user with a 403 Forbidden status code.
Finally we can define our traits which will add to the accessCondtions list as they are mixed in to the controller:
`trait PremiumUsersOnly {
self:Authentication =>
accessConditions = accessConditions :+ Conditions.isPremiumUser
}
trait BalanceCheck {
self:Authentication =>
def getRequiredBalance:Int
accessConditions = accessConditions :+ Conditions.balanceGreaterThan(getRequiredBalance)
} `
Testing:
Add a couple of users:
val users = List(User("bob@tunnelbear.com", "password", true, 10), User("alice@tunnelbear.com", "password", true, 3))
If Alice tries to access the helloUser api she will be denied:
curl -v http://localhost:9000/helloUser\?username\=alice@tunnelbear.com\&password\=password < HTTP/1.1 403 Forbidden Conditions not met: User balance must be > 8
Whereas Bob will be allowed through:
curl -v http://localhost:9000/helloUser\?username\=bob@tunnelbear.com\&password\=password < HTTP/1.1 200 OK hello bob@tunnelbear.com
Complete code samples are available here.