Overview

JWT authentication with Akka HTTP

No Comments

The authentication of RESTful APIs is quite an often asked question, so I decided to demonstrate basic authentication via JWT (JSON Web Token) in an example of an API built with Akka HTTP.

JWT working concept

Before we start with the actual coding, we should briefly recap how the mechanism of JWT authentication works. JWT itself is composed of three parts:

  • Header
  • Payload
  • Signature

Header contains a hashing algorithm (RSA, SHA256…) and token type. The payload section is going to be of most interest to you. The payload contains claims. And they usually contain information about the user along with some metadata. Lastly, JWT contains the signature which is used to verify the authenticity of the token sender. The token authentication process and access to secured resources is displayed in the following image.

RESTful APIs are stateless. Upon authentication, there is no a secured session which is generated on server and held in the context of the application followed by the cookie which is being returned and stored on the client side. In case of JWT authentication, we sign in using our credentials, the server generates an access token and we save it on the client side. In order to access secured content on the server, we have to send the access token upon each request (usually as a value of the authorization header). Then the server simply tries to verify the provided access token with a secret key. If the verification is successful, the server provides access to a secured resource. Otherwise, it rejects the request since the user is unauthorized. Our RESTful API remains stateless since the token is not stored anywhere on the server side. Also notice that payload is encoded. Therefore, it’s not smart to put sensitive data into the payload of your JWT! All this sounds great, but what about logging out? The answer is: There is no real logout. Or so to say: There is no immediate invalidation of the access token. And you will probably wonder: “But what if someone steals my access token?”. The best approach is to make your JWT access token expirable. The general rule is to make the access token short-lived and to use a refresh token (“remember me” token) for long lived sessions. Refresh token is stored as a resource (e.g. in database). When the access token expires, a refresh token is sent and checked in order to obtain a new access token. When you delete the refresh token from the data store (upon logout) and when the access token expires, then you will simply have to login again in order to obtain a new refresh and access token. For the sake of simplicity, we will only implement authentication with expirable an access token.

Akka HTTP implementation

In this example, beside the Akka HTTP library, we will use authentikat-jwt which is one of JWT implementations for Scala. Also, we will use circe and akka-http-circe JSON libraries for content unmarshalling. First, let’s create a new Scala class and its companion object – HttpApi. In the companion object, we will create a case class which will represent our login request:

final case class LoginRequest(username: String, password: String)

And we will define few properties for JWT:

private val tokenExpiryPeriodInDays = 1
private val secretKey               = "super_secret_key"
private val header                  = JwtHeader("HS256")

Expiry period will be part of claims. Now let’s add “login” route:

private def login = post {
  entity(as[LoginRequest]) {
    case lr @ LoginRequest("admin", "admin") =>
      val claims = setClaims(lr.username, tokenExpiryPeriodInDays)
      respondWithHeader(RawHeader("Access-Token", JsonWebToken(header, claims, secretKey))) {
        complete(StatusCodes.OK)
      }
    case LoginRequest(_, _) => complete(StatusCodes.Unauthorized)
  }
}

Let’s take a deeper look into this method. We are sending credentials as JSON content. Again, for the sake of simplicity, we will just check whether the username is “admin” and the password is “admin”, too. Regularly, this data should be checked in a separate service. If the credentials are incorrect, we simply return “unauthorized” HTTP status code. If the credentials are correct, we respond with “OK” status code and the JWT (the access token). JWT contains two properties in its claims:

  • user – which is just a username
  • expiredAt – the period after the JWT will not be valid anymore

Where “setClaims” method looks like this:

private def setClaims(username: String, expiryPeriodInDays: Long) = JwtClaimsSet(
  Map("user" -> username,
      "expiredAt" -> (System.currentTimeMillis() + TimeUnit.DAYS
        .toMillis(expiryPeriodInDays)))
)

The login curl command looks like this:

curl -i -X POST localhost:8000 -d '{"username": "admin", "password": "admin"}' -H "Content-Type: application/json"

And response looks something like this:

HTTP/1.1 200 OK
Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJleHBpcmVkQXQiOjE1MDI0NTkyNzUzMTV9.fgg-H47A3GswZF92Bwtvr0avkezg3TcPhCb_maYSLUY
Server: akka-http/10.0.9
Date: Thu, 10 Aug 2017 13:47:55 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 2
 
OK%

And that’s it when the login mechanism comes in. Pretty simple.

The JWT can be obtained from the “Access-Token” header and saved on client side. Upon each request, we will have to send the JWT as a value of the “authorization” header in order to access secured resources. Now, we are going to make a special directive method “authenticated” which will check JWT validity.

private def authenticated: Directive1[Map[String, Any]] =
  optionalHeaderValueByName("Authorization").flatMap {
    case Some(jwt) if isTokenExpired(jwt) =>
      complete(StatusCodes.Unauthorized -> "Token expired.")
 
    case Some(jwt) if JsonWebToken.validate(jwt, secretKey) =>
      provide(getClaims(jwt).getOrElse(Map.empty[String, Any]))
 
    case _ => complete(StatusCodes.Unauthorized)
  }

The method first tries to obtain value from the “authorization” header. If it fails to obtain that value, it will simply respond with the status “unauthorized”. If it obtains the JWT, first it is going to check whether the token expired. If the token has expired, it is going to respond with “unauthorized” status code and the “token expired” message. If the token has not expired, it will check the validity of the token and if it is valid, it will “provide” claims so that we can use them further (e.g. for authorization). In all other cases, the method will return the “unauthorized” status code. This is what the “isTokenExpired” method looks like:

private def isTokenExpired(jwt: String) = getClaims(jwt) match {
  case Some(claims) =>
    claims.get("expiredAt") match {
      case Some(value) => value.toLong < System.currentTimeMillis() 
      case None        => false
    }
  case None => false
}

And this is how “getClaims” method looks like:

private def getClaims(jwt: String) = jwt match {
  case JsonWebToken(_, claims, _) => claims.asSimpleMap.toOption
  case _                          => None
}

Finally, we can apply our “authenticated” directive on some route which will make it secured requiring access token:

private def securedContent = get {
  authenticated { claims =>
    complete(s"User ${claims.getOrElse("user", "")} accessed secured content!")
  }
}

Final route will look like this:

def routes: Route = login ~ securedContent

In order to access resources returned by the “securedContent” route, we will have to provide the JWT we got upon login as a value of “Authorization” header:

curl -i localhost:8000 -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJleHBpcmVkQXQiOjE1MDI0NTkyNzUzMTV9.fgg-H47A3GswZF92Bwtvr0avkezg3TcPhCb_maYSLUY"

And the response will look like this:

HTTP/1.1 200 OK
Server: akka-http/10.0.9
Date: Thu, 10 Aug 2017 14:08:28 GMT
Content-Type: application/json
Content-Length: 38
 
"User admin accessed secured content!"%

After we have finished with the building of routes, we are going to finally add the code in the HttpApi class which is going to be an actor used to “spawn” Akka HTTP server:

final class HttpApi(host: String, port: Int) extends Actor with ActorLogging {
  import HttpApi._
  import context.dispatcher
 
  private implicit val materializer = ActorMaterializer()
 
  Http(context.system).bindAndHandle(routes, host, port).pipeTo(self)
 
  override def receive: Receive = {
    case ServerBinding(address) =>
      log.info("Server successfully bound at {}:{}", address.getHostName, address.getPort)
    case Failure(cause) =>
      log.error("Failed to bind server", cause)
      context.system.terminate()
  }
}

A full working example can be found here.

Branislav Lazic

Branislav is a software developer at codecentric’s Doboj office since June 2015. He is primarily working with Java technologies. But he’s also interested in Scala and Akka.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

More content about Akka

Comment

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