JWT Token using Ktor and Kotlin

neverloveddev

Andjic Djordje

Posted on June 2, 2024

JWT Token using Ktor and Kotlin

The backstory

I am a big lover of Kotlin since I saw it 2018. Since then I always dreamed about having it everywhere just like we have JavaScript for everything today: on the mobile, front - end, back - end and everything else. Given that we already have Multi Platform Compose, we are already set on the front - end part and mobile part but still waiting for the server side Kotlin. Then Ktor was introduced which gave us the possibility to write server side pretty fast and neatly ( see my previous post for how blazingly fast it is) and it is the time to talk about security. In this article I managed to create a jwt token authentication ( this is done with the help of youtube video : https://www.youtube.com/watch?v=LWVgof52BBg)

Setting up the project

Using Ktor project setup you just have to add Authenticate and AuthenticateJWT packages to be able to use them. Next, inside the application.conf file ( yaml does not work for some reason) add this block:

jwt{
  domain= "https://jwt-provider-domain/"
  audience= "jwt-audience"
  realm= "ktor sample app"
  secret= "myLittleSecret"
  issuer= "kotr sample app"
}
Enter fullscreen mode Exit fullscreen mode

Steps

First step, let us load our configuration variables from the config file in the Application.kt

 val config = HoconApplicationConfig(ConfigFactory.load())
    val secret = config.property("jwt.secret").getString()
    val issuer = config.property("jwt.issuer").getString()
    val audience = config.property("jwt.audience").getString()
    val myRealm = config.property("jwt.realm").getString()
    configureSerialization()
    configureDatabases()
    configureHTTP()
    configureSecurity(secret = secret, issuer = issuer, audience = audience, myRealm = myRealm)
    configureRouting(secret = secret, issuer = issuer, audience = audience)
Enter fullscreen mode Exit fullscreen mode

As you see it, we are using these variables to configure the security and the routing as well. Secondly, we are going to configure security of our application.

fun Application.configureSecurity(secret: String, issuer: String, audience: String, myRealm: String) {
    install(Authentication) {
        jwt {
            realm = myRealm
            verifier(JWT.require(Algorithm.HMAC512(secret)).withAudience(audience).withIssuer(issuer).build())
            validate { jwtCredential: JWTCredential ->
                kotlin.run {
                    val userName = jwtCredential.payload.getClaim("userName").asString()
                    if (userName.isNotEmpty()) {
                        JWTPrincipal(jwtCredential.payload)
                    } else {
                        null
                    }
                }
            }
            challenge { _, _ ->
                call.respond(
                    HttpStatusCode.Unauthorized,
                    GenericResponse(isSuccess = true, data = "Token is not valid or has expired")
                )
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let us explain what is going on here. We are "installing" the aunthentication with the jwt with our user's username as the claim payload.We are validating everything using JWTPrincipal class ( everything explained is under validate function. In the challenge function we are sending the response when the token is not valid.

Third and finally, we are now going to use this token. Let us go to our routes file and create two simple routes, one to get the token and the other to get the user's data using that token.

fun Application.JwtRoutes(secret: String, issuer: String, audience: String){
    routing {
        get("/test"){
            call.respond(HttpStatusCode.OK,GenericResponse(true,"Hello World"))
        }
        post("/token"){
            val user:UserEntity = call.receive()

            val generatedToken = JWT.create()
                .withAudience(audience)
                .withIssuer(issuer)
                .withClaim("userName", user.userName)
                .withClaim("email", user.email)
                .sign(Algorithm.HMAC512(secret))

            val token = generatedToken
            call.respond(HttpStatusCode.OK,GenericResponse(true,token))
        }

        authenticate {
            get("/token"){
                val principal = call.principal<JWTPrincipal>()
                val username = principal!!.payload.getClaim("userName").asString()
                val email = principal.payload.getClaim("email").asString()

                val user = UserEntity(1,username,"User",email,"")
                call.respond(HttpStatusCode.OK,GenericResponse(true, data =user))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

After running our server, we are first getting the token as shown below

Image description

After passing it as Bearer Token into Postman and trying to get our data we get it successfully

Image description

Conclusion

Getting started with Ktor and Kotlin is not easy as there is not much community out there that is using this technology professionally and building client software. But it does have a great potential to become a peer competitor with the technologies we love and use daily.

💖 💪 🙅 🚩
neverloveddev
Andjic Djordje

Posted on June 2, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related