In this blog, we will discuss how to implement Akka HTTP authorization using JWT. So first let’s see what is JWT.
What is JWT
JSON Web Token (JWT) defines a compact and self-contained way for securely transmitting information between parties as a JSON object. The token is mainly composed of header, payload, signature. These three parts are separated by dots(.). The header contains a hashing algorithm eg. RSA, SHA256, etc and token type. 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.JWT defines the structure of information we are sending from one party to another.
In authentication, when the user successfully logs in using their credentials, From the server to the client JSON Web Token will be returned with the response in order to access protected routes or resources. We have to send the access token with each request 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. But what if someone steals your access token?. The best idea is to make your JWT access token expirable. When the access token expires, then you will have to login again in order to obtain a new access token. Let’s see the implementation of Akka HTTP authorization using JWT. The following diagram shows a better understanding of the concept.
Let’s see how to implement it to protect Akka HTTP routes And how to implement Akka HTTP authorization using JWT.
Firstly you need to add following library dependency in your build.sbt
libraryDependencies += "com.jason-goodwin" %% "authentikat-jwt" % "0.4.5"
Generate token
Let’s create a login route first it takes a login request and sends it to service to check if credentials are valid or not and if login is successful generate token and give response to the user with generated token.
pathPrefix("user") {
path("login") {
(post & entity(as[LoginRequest])) { loginRequest =>
if (service.userLogin(loginRequest) == "Login Successful") {
val token = TokenAuthorization.generateToken(loginRequest.email)
complete((StatusCodes.OK, token))
} else {
complete(StatusCodes.Unauthorized)
}
}
private val secretKey = "super_secret_key"
private val header = JwtHeader("HS256")
private val tokenExpiryPeriod = 1
These are some fields required while creating an object of JsonWebToken. The method generateToken will create a JsonWebToken object.
def generateToken(email: String): String = {
val claims = JwtClaimsSet(
Map(
"email" -> email,
"expiredAt" -> (System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(tokenExpiryPeriodInDays))
)
)
JsonWebToken(header, claims, secretKey)
}
JsonWebToken takes JwtClaimsSet object as a payload. And it also takes a secret key, and then use that key to sign a JWT with some claims that you provide.
Verify token
Now we need to verify that token is correct and it is not expired while accessing secured routes. So let’s create a method that will verify token.
def authenticated: Directive1[Map[String, Any]] = {
optionalHeaderValueByName("Authorization").flatMap { tokenFromUser =>
val jwtToken = tokenFromUser.get.split(" ")
jwtToken(1) match {
case token if isTokenExpired(token) =>
complete(StatusCodes.Unauthorized -> "Session expired.")
case token if JsonWebToken.validate(token, secretKey) =>
provide(getClaims(token))
case _ => complete(StatusCodes.Unauthorized ->"Invalid Token")
}
}
}
The method first gets value from the request “authorization” header. If it obtains the JWT, first it checks whether the token expired, If the token has expired, it responds with “token expired” and If the token is not expired it checks token is valid or not. JsonWebToken has an inbuilt method validate that takes the token and secret key and validates token is valid or not.
private def isTokenExpired(jwt: String): Boolean =
getClaims(jwt).get("expiredAt").exists(_.toLong < System.currentTimeMillis())
private def getClaims(jwt: String): Map[String, String] =
JsonWebToken.unapply(jwt) match {
case Some(value) => value._2.asSimpleMap.getOrElse(Map.empty[String, String])
case None => Map.empty[String, String]
}
The method isTokenExpired checks whether the token is expired. Now let’s see how to use the authenticated method to validate a protected route.
path("protectedcontent") {
get {
TokenAuthorization.authenticated { _ =>
val response = service.protectedContent
complete(response)
}
}
}
To access the above route we need to pass the token in header let’s see how to run the above code in postman. In order to access login route use following url. Send request with the POST method and pass JSON object which will contain credentials that need to log in.
0.0.0.0:8081/user/login
It will give token in response as follow.
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IlN0cmluZyIsImV4cGlyZWRBdCI6MTU4Mzg2MDg4ODgxOX0.RVDh4U9UpYh60WF5UwybjpugqiYxRjNCfS45GYj76Lk"
Now while accessing the protected route send this jwt token in header of http request. To send request Click on Authorization in select TYPE as a Bearer token and paste above in the front of token.
Get full code from here
References
https://www.jsonwebtoken.io/
https://blog.codecentric.de/en/2017/09/jwt-authentication-akka-http/