//Karthik Srinivasan

Product Engineer, CTO & a Beer Enthusiast
Experiments, thoughts and scripts documented for posterity.

Quirky Personal Projects

LinkedIn

Email me

Spray.io REST service - Authentication - Lesson 5

View the lessons list at https://github.com/karthik20522/SprayLearning

"Directives" are small building blocks of which you can construct arbitrarily complex route structures. A directive does one or more of the following:
More detailed information on directives can be found at http://spray.io/documentation/1.2-M8/ or here Spray-routing/predefined-directives-alphabetically/. In future examples, I would be using Directives so basic understanding would be useful. For this example I would be using Authentication directive to validate the request with a username and password as part of the header.

So, for authentication a new UserAuthentication trait is created which has a function that returns a ContextAuthenticator. The authenticate directive expects either a ContextAuthenticator or a Future[Authenication[T]]. At the crust of ContextAuthenticator is basically Future[Authenication[T]].

Below code that describes the authentication directive basically takes in a ContextAuthenticator and returns a Future of Authentication type of Either Rejection or an Object of type T. What this means is that Either a Rejection is returned (Left) like FailedAuthenticationRejection when credentials are missing or failed; or when successful an object is returned (Right)
    package object authentication {
        type ContextAuthenticator[T] = RequestContext => Future[Authentication[T]]
        type Authentication[T] = Either[Rejection, T]
        //. . . .
    }

The UserAuthentication trait has two functions, one that returns a ContextAuthentication and other that returns a Future. In this example, I am reading the username and password from the application.conf file and validating against it.
import com.typesafe.config.ConfigFactory
import scala.concurrent.ExecutionContext.Implicits.global
import spray.routing.AuthenticationFailedRejection

case class User(userName: String, token: String) {}

trait UserAuthentication {

  val conf = ConfigFactory.load()
  lazy val configusername = conf.getString("security.username")
  lazy val configpassword = conf.getString("security.password")

  def authenticateUser: ContextAuthenticator[User] = { ctx =>
    {
      //get username and password from the url
      val usr = ctx.request.uri.query.get("usr").get
      val pwd = ctx.request.uri.query.get("pwd").get

      doAuth(usr, pwd)
    }
  }

  private def doAuth(
      userName: String,
      password: String
  ): Future[Authentication[User]] = {
    //here you can call database or a web service to authenticate the user
    Future {
      Either.cond(
        password == configpassword && userName == configusername,
        User(userName = userName, token = java.util.UUID.randomUUID.toString),
        AuthenticationFailedRejection("CredentialsRejected")
      )
    }
  }
}

Note that I am importing "scala.concurrent.ExecutionContext.Implicits.global", this is because futures require an ExecutionContext and I letting spray to use the default actor ExecutingContext. //implicit val system = ActorSystem("on-spray-can")

Now that we have the authentication setup, we need to inherit the UserAuthenticationTrait using "with" and use the "authentication" directive by passing the "authenticateUser" function that we defined.
trait CustomerService
    extends HttpService
    with Json4sSupport
    with UserAuthentication {

  val customerRoutes =
    path("addCustomer") {
      post {
        authenticate(authenticateUser) { user =>
          entity(as[JObject]) { customerObj =>
            complete {
              //. . . .
            }
          }
        }
      }
    } ~
      path("getCustomer" / Segment) { customerId =>
        get {
          authenticate(authenticateUser) { user =>
            {
              complete {
                //. . .
              }
            }
          }
        }
      }
}

    [Success GET] http://localhost:8080/getCustomer/520ed44941f19472d5f?usr=karthik&pwd=kufli
    [Failed GET] http://localhost:8080/getCustomer/520ed44941f19472d5f9?usr=fail&pwd=wrong


Further readings on this topic:
Spray/routing/authentication/HttpAuthenticator.scala
Spray/routing/SecurityDirectivesSpec.scala
https://groups.google.com/forum/#!topic/spray-user/5DBEZUXbjtw
Spray/routing/RejectionHandler.scala