Normally, authentication in a play project is done via a non-standard login page and then storing a user id or similar in the session cookie. Then, each request will pick up the user from the cookie and continue.
However, for unit testing and other automated interactions, it is important that Basic http authentication is supported as well, per each individual request (i.e. no cookies). Yes, there are complicated web testing frameworks that support cookies and stuff, but sometimes I just want to write a one liner test like "private pages can only be seen by registered users" without learning complex frameworks and DSLs whatnot.
Read more about basic http authentication here. The crux of it is that the username and password are passed together in the http request header in this form:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Note that the Snakked library supports basic authentication, read more here - you'll see in the next blog how easy that makes testing Play applications.
The normal way to authenticate in Play is via some function that gets the User from the Request, so [Request => User]
. In my case, I called this auth with the signature:
def auth(implicit request: Request[_]): Option[User] = {
val au = RazController.auth(request.session.get("userId"), request.headers.get("Authorization"))
}
It essentially looks at either the session cookie to have a value for "userId" OR look for the Basic http authentication headers and get the username/password. The first is the normal cookie obtained with the login page, the second is the alternative for unit testing.
The code to process the basic header is:
val e2 = headerValue.replaceFirst("Basic ", "")
val e3 = new String(Base64 dec e2) //new sun.misc.BASE64Decoder().decodeBuffer(e2)
val EP = """([^:]*):(.*)""".r
val EP(user, pass) = e3
For the Base64 decoding, I use this:
new org.apache.commons.codec.binary.Base64(true).decode(s)
Then, with the username/password, just go find your userId in your database...
This opens up your server to attack - while the login page can make use of a CAPTCHA, anyone can write a simple script to get a page and randomly test username password combinations - how you prevent that is up to you... it can be as simple as an exponential backoff or limit errors to like 3 or something and ban that IP for a while or similar...
Another way (one of my preferred implementation) is to only allow it from known/trusted IP addresses, like 'localhost' and use it in unit testing only. In case you're wondering, I have a different scheme implemented :) but I'm not telling you... you hacker, you!
See this for other approaches stackoverflow.com/questions/14356545/how-to-implement-http-basic-auth-in-play-framework-1-2
Enjoy!