Monadic authentication in Scala

October 11, 2015

Authenticating an operation involves identification of the owner of the context of its execution. It can simply be modeled as a function that takes a principal as input.

An authenticated action can be modelled several ways, including:

Let's represent authenticated actions as functions that takes an email address and produces a result:

type AuthN[A] = Reader[EmailAddr,A]

Here, EmailAddr is a newtype for a String containing an email address.

To evaluate an authenticated action, an interpreter asks the environment to authenticate the user, and if it can, applies the function with the authentication token. If not, it emits an unauthenticated error:

def authenticate(req: HttpServletRequest): Option[EmailAddr] = ...

authenticate(req) match {
  case None =>
    val (status, body) = ... // error response
    res.respond(status = status, body = body)
  case Some(e) =>
    val (status, body) = ... // apply the action with e
    res.respond(status = status, body = body)
}

Because authenticated actions are monadic, they can be combined into bigger, more complex authenticated actions:

case class AccountInfo(name: String, email: String, website: String)

val get: AuthN[AccountInfo] = ...
val save: AccountInfo => AuthN[Unit] = ...

def updateWebsite(newWebsite: String): AuthN[Unit] =
  for {
    oldAccountInfo <- get
    newAccountInfo  = oldAccountInfo.copy(website = newWebsite)
    _              <- save(newAccountInfo)
  } yield ()

Alternatively:

def updateWebsite(newWebsite: String): AuthN[Unit] =
  get map { _.copy(website = newWebsite) } >>= save