Saturday, October 6, 2018

Simple way to secure Akka Http rest services

One of the differences between Play Framework and Akka Http it is a security of the rest services. Akka Http has almost nothing for that. So, let's assume the next behavior for our application:
  •  All rest services except login should be secured
  • Login service returns JWT token as part of the header
  • Before to access any other services we have to validate JWT token in the header of a request
Our route without security has next view:
lazy val routes: Route =
pathPrefix("api") {
    pathPrefix("login") {
      post {
        parameter("email", "password") { (email, password) =>
          val loginResult: Future[Boolean] = loginService.login(email, password)
          onSuccess(loginResult) { result =>
            if(result) {
              respondWithHeader(RawHeader("TOKEN", createJwtToken(email))) {
                complete(StatusCodes.OK)
              }else{
                complete(StatusCodes.Unauthorized)
              }
            }
          }
        }
      }
    } ~
    pathPrefix("myService") {
      val data = myService.search()
      complite(data)
    }      
}
Now let's write a method which will wrap invocation of all services which we want to secure:
def authenticated: Directive1[String] =
    Directives.optionalHeaderValueByName("TOKEN").flatMap {
      case Some(jwt) if !isJwtTokenValid(jwt) =>
        Directives.complete(StatusCodes.Unauthorized -> "Invalid token.")
      case Some(jwt) if isJwtTokenExpired(jwt) =>
        Directives.complete(StatusCodes.Unauthorized -> "Token expired.")

      case Some(jwt) if !isJwtTokenExpired(jwt) => decodeJwtToken(jwt) match {
        case Some(email) =>
          Directives.provide(email)
        case None =>
          Directives.complete(StatusCodes.Unauthorized)
      }
      case _ => Directives.complete(StatusCodes.Unauthorized)
    }
And the last step is to invoke it before our services:

pathPrefix("api") {
    pathPrefix("login") {
      ...
    } ~
    authenticated{email =>    
      pathPrefix("myService") {
        val data = myService.search()
        complite(data)
      }      
    }
}