Categories
Programming

Creating JWT authentication for your akka http server

JSON Web Token (JWT) is an Internet standard for creating JSON-based access tokens that assert some number of claims. For example, a server could generate a token that has the claim “logged in as admin” and provide that to a client. The client could then use that token to prove that it is logged in as admin. The tokens are signed by one party’s private key (usually the server’s), so that both parties (the other already being, by some suitable and trustworthy means, in possession of the corresponding public key) are able to verify that the token is legitimate.

In this post, we’ll see how we can implement a JWT authentication with the akka-http server in Scala.

This example uses the scala-jwt library for creating / validating the claims.

Bringing in the project dependencies

Apart from akka-http, we’ll need to import the scala-jwt to our project

libraryDependencies ++= Seq(
  "com.pauldijou"    %% "jwt-core"               % "4.2.0",
  "com.pauldijou"    %% "jwt-json4s-jackson"     % "4.2.0",
  "com.pauldijou"    %% "jwt-json4s-native"      % "4.2.0"
)

Now, lets create a simple JWTAuthService with the following methods:

  • setClaims
  • getClaims
  • authenticated
  def setClaims(user: String): JObject =
    JObject(
      ("user", JString(user)),
      ("whatever", JString("you can store anything"))
    )

You can pretty much add anything here in the setClaims, its going to be encoded and then sent to the client.

  private def getClaims(jwt: String): Map[String, String] ={
    JwtJson4s.decodeJson(jwt, secretKey, Seq(algo)).toOption.map(j => j.extract[Map[String, String]]).getOrElse(Map.empty)
  }

getClaims decodes the claim and deserializes it into a Map here.

Map would look like:

Map("user" -> "admin", "whatever" -> "you can store anything")
  def authenticated: Directive1[Map[String, Any]] =
    optionalHeaderValueByName("Access-Token").flatMap {
      case Some(jwt) =>
        val claim = getClaims(jwt)
        if(claim.nonEmpty && claim.get("user").nonEmpty){
           provide(claim)
         } else complete(StatusCodes.Unauthorized)
      case _ => complete(StatusCodes.Unauthorized)
    }

authenticated is the directive that validates the claim and deserialize the claim into a scala Map, which can be useful later. You can add more layers of verification here.

The JWTAuthService also defines which algorithm is used for creating the signature with a secret key.

Let’s now create a simple route with 2 endpoints:

  • login
  • adminPanel

The client would require to login first to get the access token (from the JWTAuthService) for the adminPanel.

  private val login =
    path("login"){
      post {
        entity(as[User]) { user: User => {
          val validUser = verifyUser(user)
          if (validUser) {
            val claims = setClaims(user.username)
            respondWithHeader(RawHeader("Access-Token", getJwtToken(claims))) {
              complete(StatusCodes.OK)
            }
          } else {
            complete(StatusCodes.Unauthorized)
          }
        }}
      }
    }
  def verifyUser(user: User): Boolean ={
    user match {
      case User("admin", "password") => true
      case _ => false
    }
  }
  private val adminPanel =
    path("adminPanel") {
      get {
        authenticated { claims => {
          complete(s"Hello, Admin!")
        }}
      }
    }

Now, lets see how this works:

Create a login request with valid credentials (admin, password) to receive the Access-Token:

login request with invalid credentials:

now lets access the adminPanel with the valid token we obtained from the first request:

You can find the complete code example here.

Leave a Reply

Your email address will not be published. Required fields are marked *