Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

JWT authentication with Akka HTTP

19.9.2017 | 6 minutes of reading time

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:

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

And we will define few properties for JWT:

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

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

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

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:

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

The login curl command looks like this:

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

And response looks something like this:

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

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.

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

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:

1private def isTokenExpired(jwt: String) = getClaims(jwt) match {
2  case Some(claims) =>
3    claims.get("expiredAt") match {
4      case Some(value) => value.toLong < System.currentTimeMillis() 
5      case None        => false
6    }
7  case None => false
8}

And this is how “getClaims” method looks like:

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

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

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

Final route will look like this:

1def 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:

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

And the response will look like this:

1HTTP/1.1 200 OK
2Server: akka-http/10.0.9
3Date: Thu, 10 Aug 2017 14:08:28 GMT
4Content-Type: application/json
5Content-Length: 38
6 
7"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:

1final class HttpApi(host: String, port: Int) extends Actor with ActorLogging {
2  import HttpApi._
3  import context.dispatcher
4 
5  private implicit val materializer = ActorMaterializer()
6 
7  Http(context.system).bindAndHandle(routes, host, port).pipeTo(self)
8 
9  override def receive: Receive = {
10    case ServerBinding(address) =>
11      log.info("Server successfully bound at {}:{}", address.getHostName, address.getPort)
12    case Failure(cause) =>
13      log.error("Failed to bind server", cause)
14      context.system.terminate()
15  }
16}

A full working example can be found here .

share post

Likes

0

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.