10

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?

Andrew Swan
  • 12,737
  • 19
  • 64
  • 97
Prakash
  • 548
  • 5
  • 19

6 Answers6

12

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.

centr
  • 625
  • 7
  • 15
  • i have added a re-worked copy of this code into a separate answer. Thanks, the mechanics of this work nicely as expected. I dislike the use of " " and ":" in string splits as simple character separators would suffice and avoid regex-overhead. Also the filter with predicate is neater on a single line with `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 :-) – Peter Perháč Jan 11 '17 at 00:05
  • Passwords with `:` would be split multiple times and rejected. – Niklas Feb 03 '18 at 20:47
6

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""""))
    }
  }
Peter Perháč
  • 19,614
  • 21
  • 116
  • 148
5

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

Krzysztof Atłasik
  • 18,129
  • 4
  • 42
  • 64
Didac Montero
  • 1,946
  • 17
  • 26
3

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.

Peanut
  • 3,195
  • 2
  • 27
  • 43
3

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.

marvin82
  • 206
  • 3
  • 11
3

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]
Henry
  • 2,070
  • 22
  • 16