Securing REST response serialization with Scala implicits
Quite often when developing web apps and REST/HTTP APIs using Scala, it’s very convenient to automatically serialize any case class
as a response to the client. However, it’s then equally easy to send too much data to the client, and this can be a security risk. For example: after logging in a user, if instead of sending partial user data (only what the frontend needs), we send the whole user object with the (hopefully!) hashed password, salt, security questions etc.
How can we prevent such situations? We need to somehow limit what can be serialized as a response. Here we could use Scala’s implicits to control this particular capability. Let’s define a marker trait:
trait CanBeSerialized[T]
An instance of this trait has quite obvious meaning: given an instance of CanBeSerialized[T]
, we know that any instances of type T
can be serialized and sent as a response to the client. Now, we need to somehow limit our serialization logic only to the types for which a CanBeSerialized
instance exists. We do this by requiring an implicit parameter in the serialization method:
object SerializeToJson {
def serialize[T](t: T)(implicit cbs: CanBeSerialized[T]): String = {
// serialize
}
}
If we now try to call serialize
on a type for which the implicit doesn't exist (or is not in scope), we’ll get a nice compiler error (saying that an implicit value for e.g. CanBeSerialized[User]
cannot be found, which is even quite self-explanatory). Note that the actual value of cbs
doesn’t matter and is never used. It’s enough that the implicit exists at all.
How do we provide the implicit values? Scala looks for implicits in many places, but the two most useful here are the lexical scope, and the companion object of the type. For example we could have:
case class UserWebView(username: String, …)
object UserWebView {
// only the existence of an instance matters, not the actual value
implicit val cbs = new CanBeSerialized[UserWebView] {}
}
As the compiler automatically checks the companion objects when looking for implicits, this will work any time we pass in a UserWebView
instance to the serialize
method; no additional imports etc required! And because the value is implicit, our code is not cluttered with explicit can-be-serialized parameters.
We now have tight control of which case classes can be serialized and sent back to the client.
This approach is implemented in Bootzooka and integrated into an akka-http directive. Take a look at JsonSupport
, where the CanBeSerialized
trait is defined, and then used in the serialization (marshall
) method. In UserRoutes
you can see a specific can-be-serialized instance defined for the types used in responses of these routes. Because the values are implicit, they do not clutter the code where the routes are defined.
By the way: this is a very simple example of using typeclasses in Scala.