Ólafur Páll Geirsson

Authorization with Autowire

24 Oct 2015. 3 minute read.

I’ve been playing with Scala.js lately. I came across Autowire, an excellent RPC library for typesafe server-client communication. However, I couldn’t find any examples of how to bake authorization into my RPC calls. The following example is a simple solution that is agnostic to your application stack and doesn’t pollute your pretty method signatures.

TL;DR. Inject the User/authorization object into your API implementation and create a new server instance for each RPC call.

This post assumes you understand how Autowire works. The “trick” may appear obvious in hindsight, but it took me a while to figure out.

So let’s begin, we want to restrict access to an AdminApi

// Shared project.
trait AdminApi {
  def doSecretThing(secret: String): String
}

case class HttpError(status: Int) extends Exception
object UnauthorizedError extends HttpError(401)

case class User(roles: List[String])

In this example we will throw an exception in case authorization fails. If you don’t like exceptions, I’m sure you can figure out how to use Either instead.

On the server side, we implement the interface

// Server project.
class AdminApiImpl(user: User) extends AdminApi {
  // Authorization run in constructor for all methods.
  if (!user.roles.contains("admin")) throw UnauthorizedError

  def doSecretThing(secret: String): String =
    // Authorization run only for this method.
    if (user.roles.contains("superadmin")) secret
    else throw UnauthorizedError

}

Most importantly, our AdminApi implementation has a user member. That is, we create a new instance of our API for every RCP request. With this setup, we can run authorization logic either in methods or the constructor.

We implement the server in a similar fashion.

// Server project.
class MyServer(user: User) extends autowire.Server[...] {
  def write[...](r: AutowireResult) = ...
  def read[...](p: String) = ...

  // This is the interesting bit.
  val adminApi = route[AdminApi](new AdminApiImpl(user))

  val routes = adminApi
}

The triple dots ... hide some parts that are irrelevant for the purpose of this example. The key bit is that the server also has a user member, like AdminApiImpl.

We can compose multiple APIs together like this.

// Server project, inside MyServer
val adminApi = route[AdminApi](new AdminApiImpl(user))
val basicApi = route[BasicApi](BasicApiImpl)
val routes = adminApi orElse basicApi

In our request handler, we create a new instance of MyServer for every rpc call. The following piece is implemented in the Play framework, but you can adapt it to your needs. For example, if you are using Spray as a server or Silhouette for authentication, your request handler will look differently.

// Server project.
object Application extends Controller {
  def autowireApi = Action.async { request =>
    try {
      // Made up method name, this will depend on
      // your stack.
      val user: User = getUserFromRequest(request)
      // Use your favorite serialization format.
      val autowireRequest = ...
      // The important bit, pass user into server.
      new MyServer(user).routes(autowireRequest)
                        .map { txt =>
        Ok(txt)
      }
    } catch {
      // Standard http response in case of failure.
      case HttpError(s) =>
        Future.successful(new Status(s))
    }
  }
}

From the client, invoke the RPC like usual

// Client project.
MyClient[AdminApi].doSecretThing("I am an admin.")
                  .call().map { msg =>
  println(msg) // "I am an admin."
}.recover {
  case AjaxException(xhr) if xhr.status == 401 => {
    println("I am not an admin.")
  }
}

There we go, no frills authorization with Autowire. You should be able adapt this idea to your favorite application stack. If you can think of a better way to accomplish the same thing, I would be happy to hear about it.

For more information on writing a SPA with Scala.js, I recommend this tutorial.

About: full-stack Scala, webapps, security