Skip to content

Instantly share code, notes, and snippets.

@EdgeCaseBerg
Forked from dk8996/BasicAuthFilter.scala
Last active April 28, 2016 21:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EdgeCaseBerg/e4bdec2c2907f40b4b72 to your computer and use it in GitHub Desktop.
Save EdgeCaseBerg/e4bdec2c2907f40b4b72 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.drop(1).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>
@oscar-g
Copy link

oscar-g commented Apr 28, 2016

Quick question...couldn't you use a flatMap on 64 and replace line 70 with case _ => None?

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