Play Framework REST with basic authentication and SSL
Asked Answered
R

6

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?

Revivalist answered 7/4, 2015 at 12:33 Comment(0)
G
13

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.

Grisgris answered 8/4, 2015 at 2:46 Comment(2)
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 :-)Mister
Passwords with : would be split multiple times and rejected.Suziesuzuki
N
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""""))
    }
  }
Nissie answered 10/1, 2017 at 23:54 Comment(0)
W
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

Whiff answered 9/4, 2015 at 7:46 Comment(0)
H
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.

Hurter answered 7/4, 2015 at 13:25 Comment(0)
S
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.

Showmanship answered 7/4, 2015 at 13:31 Comment(0)
A
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]
Astronomy answered 15/11, 2016 at 21:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.