I am new to this authentication area. I searched a lot but was not able to find a way to authenticate the REST calls made to the Play server. What are the various ways and best practice?
A very easy way is to use Action Composition. For a sample, take a look at this Gist provided by Guillaume Bort: https://gist.github.com/guillaumebort/2328236. If you want to use it in an async action, you can write something like:
def BasicSecured[A](username: String, password: String)(action: Action[A]): Action[A] = Action.async(action.parser) { request =>
request.headers.get("Authorization").flatMap { authorization =>
authorization.split(" ").drop(1).headOption.filter { encoded =>
new String(org.apache.commons.codec.binary.Base64.decodeBase64(encoded.getBytes)).split(":").toList match {
case u :: p :: Nil if u == username && password == p => true
case _ => false
}
}
}.map(_ => action(request)).getOrElse {
Future.successful(Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="Secured Area""""))
}
}
SSL does not have anything to do with basic authentication. You can use HTTPS for API either directly or through a front-end HTTP server like ngnix. There are pretty good details in Play documentation on this subject.
:
would be split multiple times and rejected. –
Suziesuzuki basically, I have taken the answer from @centr and tried to make it a little more readable. See if you prefer this version of the same code. Tested thoroughly, works as expected.
def BasicSecured[A](username: String, password: String)(action: Action[A]): Action[A] = Action.async(action.parser) { request =>
val submittedCredentials: Option[List[String]] = for {
authHeader <- request.headers.get("Authorization")
parts <- authHeader.split(' ').drop(1).headOption
} yield new String(decodeBase64(parts.getBytes)).split(':').toList
submittedCredentials.collect {
case u :: p :: Nil if u == username && p == password => action(request)
}.getOrElse {
Future.successful(Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="Secured Area""""))
}
}
If we are just talking about basic auth, you don't need any external module. Basically, you could implement it using action composition.
Here is a full example of it.
If you also need authorization, you could simply combine the previous example with Deadbolt. It will allow you to provide access to some group of clients and deny access to others.
SSL support does not have anything to do with the authentication. However, is explained in the Play Documentation
Read the following Readme/article: Securing Single Page Apps and REST Services and check out the corresponding sample application (same link). It explains how to do what you're asking.
For Scala, Secure Social is probably the best estabilished solution. You will find plenty of documentation and examples at the given link. You can also take a look at Play2-auth as another valid option.
You will find even more possibilities on Play 2 Modules list.
If you want/need to bake your own solution, it will probably still be useful to look into code of existing solutions for inspiration and ideas. Nevertheless, my general advice towards anything related with security is NOT to implement it yourself unless you really need it (and/or really know what you're doing).
BTW, there's absolutely nothing specific about REST here. You're essentially protecting your controllers methods, so it doesn't matter whether their invocation was triggered by a REST call or not.
A filter could be used as well. The following is based on Play 2.5.
import org.apache.commons.codec.binary.Base64
override def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val auth = requestHeader.headers.get("Authorization")
val invalidResult = Future.successful(
Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="Secured"""")
)
if (auth.isEmpty) {
invalidResult
}
else {
val credentials = new String(Base64.decodeBase64(auth.get.split(" ").drop(1).head.getBytes)).split(":")
if (credentials.length < 2) {
invalidResult
}
else {
for {
authVerify <- verify(credentials(0), credentials(1))
r <- {
if (authVerify) {
nextFilter(requestHeader).map { result: Result => result }
}
else {
invalidResult
}
}
} yield {
r
}
}
}
}
def verify(username: String, password: String): Future[Boolean]
© 2022 - 2024 — McMap. All rights reserved.
collect
method. The main motivation for re-writing was the annoying triangular whitespace as the code gets deeper and deeper nested. This was calling for a comprehension :-) – Mister