1
votes

I've written an API based on Play with Scala and I'm quite happy with the results. I'm in a stage where I'm looking at optimising and refactoring the code for the next version of the API and I had a few questions, the most pressing of which is authentication and the way I manage authentication.

The product I've written deals with businesses, so exposing Username + Password with each request, or maintaining sessions on the server side weren't the best options. So here's how authentication works for my application: User authenticates with username/password. Server returns a token associated with the user (stored as a column in the user table) Each request made to the server from that point must contain a token. Token is changed when a user logs out, and also periodically. Now, my implementation of this is quite straightforward – I have several forms for all the API endpoints, each one of which expects a token against which it looks up the user and then evaluates if the user is allowed to make the change in the request, or get the data. So each of the forms in the authenticated realm are forms that need a token, and then several other parameters depending on the API endpoint.

What this causes is repetition. Each one of the forms that I'm using has to have a verification part based on the token. And its obviously not the briefest way to go about it. I keep needing to replicate the same kind of code over and over again.

I've been reading up about Play filters and have a few questions: Is token based authentication using Play filters a good idea? Can a filter not be applied for a certain request path? If I look up a user based on the supplied token in a filter, can the looked up user object be passed on to the action so that we don't end up repeating the lookup for that request? (see example of how I'm approaching this situation.)

  case class ItemDelete(usrToken: String, id: Long) {
    var usr: User = null
    var item: Item = null
  }


  val itemDeleteForm = Form(
    mapping(
      "token" -> nonEmptyText,
      "id" -> longNumber
    ) (ItemDelete.apply)(ItemDelete.unapply)
    verifying("unauthorized",
      del => {
        del.usr = User.authenticateByToken(del.usrToken)
        del.usr match {
          case null => false
          case _ => true
        }
      }
    )
    verifying("no such item",
      del => {
        if (del.usr == null) false
        Item.find.where
          .eq("id", del.id)
          .eq("companyId", del.usr.companyId) // reusing the 'usr' object, avoiding multiple db lookups
        .findList.toList match {
          case Nil => false
          case List(item, _*) => {
            del.item = item
            true
          }
        }
      }
    )
  )
2
Have you considered to use an already existing authentication solution. I can suggest you silhouette.mohiva.com. It can use bearer or Json web tokens. It provides out of the box credentials authentication and it is based on custom actions. If you have some questions please feel free and ping me. - akkie

2 Answers

3
votes

Take a look at Action Composition, it allows you to inspect and transform a request on an action. If you use a Play Filter then it will be run on EVERY request made.

For example you can make a TokenAction which inspects the request and if a token has been found then refine the request to include the information based on the token, for example the user. And if no token has been found then return another result, like Unauthorized, Redirect or Forbidden.

I made a SessionRequest which has a user property with the optionally logged in user, it first looks up an existing session from the database and then takes the attached user and passes it to the request

A filter (WithUser) will then intercept the SessionRequest, if no user is available then redirect the user to the login page

// Request which optionally has a user
class SessionRequest[A](val user: Option[User], request: Request[A]) extends WrappedRequest[A](request)

object SessionAction extends ActionBuilder[SessionRequest] with ActionTransformer[Request, SessionRequest] {

  def transform[A](request: Request[A]): Future[SessionRequest[A]] = Future.successful {
    val optionalJsonRequest: Option[Request[AnyContent]] = request match {
      case r: Request[AnyContent] => Some(r)
      case _ => None
    }

    val result = {
      // Check if token is in JSON request
      for {
        jsonRequest <- optionalJsonRequest
        json <- jsonRequest.body.asJson
        sessionToken <- (json \ "auth" \ "session").asOpt[String]
        session <- SessionRepository.findByToken(sessionToken)
      } yield session
    } orElse {
      // Else check if the token is in a cookie
      for {
        cookie <- request.cookies.get("sessionid")
        sessionToken = cookie.value
        session <- SessionRepository.findByToken(sessionToken)
      } yield session
    } orElse {
      // Else check if its added in the header
      for {
        header <- request.headers.get("sessionid")
        session <- SessionRepository.findByToken(header)
      } yield session
    }

    result.map(x => new SessionRequest(x.user, request)).getOrElse(new SessionRequest(None, request))
  }
}

// Redirect the request if there is no user attached to the request
object WithUser extends ActionFilter[SessionRequest] {
  def filter[A](request: SessionRequest[A]): Future[Option[Result]] = Future.successful {
    request.user.map(x => None).getOrElse(Some(Redirect("http://website/loginpage")))
  }
}

You can then use it on a action

def index = (SessionAction andThen WithUser) { request =>
  val user = request.user
  Ok("Hello " + user.name)
}

I hope this will give you an idea on how to use Action Composition

1
votes

The people at Stormpath has a sample Play application providing authentication via their Backend Service. Some of its code could be useful to you.

It uses username/password rather than tokens, but it should not be complex to modify that.

They have followed this Play Document: https://www.playframework.com/documentation/2.0.8/ScalaSecurity.

The specific implementation for this is here: https://github.com/stormpath/stormpath-play-sample/blob/dev/app/controllers/MainController.scala.

This Controller handles authentication operations and provides the isAuthenticated action via the Secured Trait (relying on play.api.mvc.Security). This operation checks if the user is authenticated and redirects him to the login screen if he is not:

/**
 * Action for authenticated users.
 */
def IsAuthenticated(f: => String => Request[AnyContent] => Future[SimpleResult]) =
  Security.Authenticated(email, onUnauthorized) { user =>
  Action.async { request =>
    email(request).map { login =>
      f(login)(request)
    }.getOrElse(Future.successful(onUnauthorized(request)))
  }
}

Then, each controller that needs authenticated operations must use the Secured Trait:

object MyController extends Controller with Secured

And those operations are "wrapped" with the IsAuthenticated action:

def deleteItem(key: String) = IsAuthenticated { username => implicit request =>
  val future = Future {
    MyModel.deleteItem(request.session.get("id").get, key)
    Ok
  }
  future.map(
    status => status
  )
}

Note that the deleteItem operation does not need a username, only the key. However, the authentication information is automatically obtained from the session. So, the business' API does not get polluted with security-specific parameters.

BTW, that application seems to have never been officially released so consider this code a proof of concept.