Skip to content

Instantly share code, notes, and snippets.

@dk8996
Last active March 11, 2017 09:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dk8996/1fd2497e82297dad5789 to your computer and use it in GitHub Desktop.
Save dk8996/1fd2497e82297dad5789 to your computer and use it in GitHub Desktop.
Basic Auth Filter for Play Framework
import com.typesafe.scalalogging.slf4j.Logging
import sun.misc.BASE64Decoder
import play.api.mvc._
import scala.concurrent.Future
import play.mvc.Results._
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object BasicAuthFilter extends Filter with Logging {
private lazy val unauthResult = Results.Unauthorized.withHeaders(("WWW-Authenticate",
"Basic realm=\"myRealm\""))
private lazy val passwordRequired = true
private lazy val username = "someUsername"
private lazy val password = "somePassword"
private lazy val outsidePages = Seq("ping.html")
//need the space at the end
private lazy val basicSt = "basic "
//This is needed if you are behind a load balancer or a proxy
private def getUserIPAddress(request: RequestHeader): String = {
return request.headers.get("x-forwarded-for").getOrElse(request.remoteAddress.toString)
}
private def logFailedAttempt(requestHeader: RequestHeader) = {
logger.warn(s"IP address ${getUserIPAddress(requestHeader)} failed to log in, " +
s"requested uri: ${requestHeader.uri}")
}
private def decodeBasicAuth(auth: String): Option[(String, String)] = {
if (auth.length() < basicSt.length()) {
return None
}
val basicReqSt = auth.substring(0, basicSt.length())
if (basicReqSt.toLowerCase() != basicSt) {
return None
}
val basicAuthSt = auth.replaceFirst(basicReqSt, "")
//BESE64Decoder is not thread safe, don't make it a field of this object
val decoder = new BASE64Decoder()
val decodedAuthSt = new String(decoder.decodeBuffer(basicAuthSt), "UTF-8")
val usernamePassword = decodedAuthSt.split(":")
if (usernamePassword.length >= 2) {
//account for ":" in passwords
return Some(usernamePassword(0), usernamePassword.splitAt(1)._2.mkString)
}
None
}
private def isOutsideSecurityRealm(requestHeader: RequestHeader): Boolean = {
val reqURI = requestHeader.uri
if (reqURI.length() > 0) {
//remove the first "/" in the uri
return outsidePages.contains(reqURI.substring(1))
}
false
}
def apply(nextFilter: (RequestHeader) => Future[SimpleResult])(requestHeader: RequestHeader):
Future[SimpleResult] = {
if (!passwordRequired || isOutsideSecurityRealm(requestHeader)) {
return nextFilter(requestHeader)
}
requestHeader.headers.get("authorization").map { basicAuth =>
decodeBasicAuth(basicAuth) match {
case Some((user, pass)) => {
if (username == user && password == pass) {
return nextFilter(requestHeader)
}
}
case _ => ;
}
logFailedAttempt(requestHeader)
return Future.successful(unauthResult)
}.getOrElse({
logFailedAttempt(requestHeader)
Future.successful(unauthResult)
})
}
}
@()(implicit request: RequestHeader)
<html><body>ping from: @{request.remoteAddress}</body></html>
@dk8996
Copy link
Author

dk8996 commented Jun 18, 2014

@EdgeCaseBerg
Copy link

There's a bug at line 43. When you use split on the string it removes what you split on, so your code above does this:

val s = "user:passwith:init"
val arr = s.split(":") //Array[String]("user","passwith","init")
val result = arr.splitAt(1)._2.mkString // "passwithinit"

What you need to do is combine the string back together with a colon like so:

val s = "user:passwith:init"
val arr = s.split(":") //Array[String]("user","passwith","init")
val result = arr.splitAt(1)._2.mkString(":") // "passwith:init"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment