DEV Community

Jan Stoltman
Jan Stoltman

Posted on

Ktor - Setting up JWT

JWT can be a tricky thing to implement yourself, fortunately you don't really have to do that, it's much easier, safer and faster to use one of many existing libraries.

Most frameworks have such libraries, and so does Ktor. In this post I would like to save the knowledge on how to quickly setup basic JWT flow in your app.

0. Add gradle import and install feature

implementation "io.ktor:ktor-auth-jwt:$ktor_version"
Enter fullscreen mode Exit fullscreen mode
 install(Authentication) { jwt { } }
Enter fullscreen mode Exit fullscreen mode

1. Setup secret and config

Preferably you should use standard application.conf file to store configuration info. If so, just add your cypher's secret and other jwt related configs there (don't push it to control version system)

jwt {
  "SECRET" = "123"
  "VALIDITY_MS" = "36000000" // 10 Hours
  "ISSUER" = "softwaret"
  "REALM" = "softwaret.kpdf"
}
Enter fullscreen mode Exit fullscreen mode

2. Prepare token generation method

Pick user-related data that you want to store in token and then prepare token generation method (I use just login in my example)

fun generateToken(login: Login): String = JWT.create()
        .withSubject("Authentication")
        .withIssuer(issuer)
        .withClaim("login", login.value)
        .withExpiresAt(obtainExpirationDate())
        .sign(algorithm)

private fun obtainExpirationDate() = Date(System.currentTimeMillis() + tokenExpirationPeriodMs) # expiration period from config file
Enter fullscreen mode Exit fullscreen mode

3. Pass token to user after login

Return the new token in any way you like and let client set it as its Authorization header with value Bearer $token (eg Authorization: Bearer 123).

4. Setup token validation

Add body to installed JWT application's feature

 jwt {
            realm = "jwt.REALM" #realm from config file
            verifier(buildJwtVerifier())
            validate { jwtCredential -> validateCredential(jwtCredential) }

        }
}
Enter fullscreen mode Exit fullscreen mode

Verifier should check whether token is correct and can be trusted, fortunately the framework takes care of this for us, just create a function which build the verifier:

fun buildJwtVerifier() = 
    JWT.require(Algorithm.HMAC512("jwt.SECRET")) # secret from config file 
        .withIssuer(issuer) # issuer from config file 
        .build()
Enter fullscreen mode Exit fullscreen mode

Validator should check whether any user (or any other entity in your app) matches the passed data (login in my example) and sets it as the call's principle. This principle with be later available for any call handling method which requires authentication, so that one can easily check wich user asked about data.

fun validateCredential(jwtCredential: JWTCredential) =
    if (userByLoginExists(jwtCredential.payload.getClaim("login")) {
        JWTPrincipal(jwtCredential.payload)
    } else {
        null
    }
Enter fullscreen mode Exit fullscreen mode

5. Put any route which requires authentication inside authenticate block

Just wrap any route in authenticate lambda and it will easily handle JWT authentication and return 401 if token is invalid.

authenticate {
            get("/authentication") {
                call.respond(message = "Authentication is valid!")
            }
        }
Enter fullscreen mode Exit fullscreen mode

And that's it! Happy JWT-ing in the future!

Real life code example of JWT implementation can by found here: github.com/Softwaret/kpdf

Top comments (4)

Collapse
 
viktorpetrovski profile image
Viktor Petrovski

Hi, good read well defined and on point, I dig it!
Have you done something with refresh token logic? I'm working on a REST api app using ktor and I need to implement refresh token logic since the standard JWT library does not support this out of the box do you have any recommendation?

Collapse
 
ojaynico profile image
Nicodemus Ojwee

Nice and clear article.
Is it possible to implement user roles and authorize each user to a specific route?

Collapse
 
samar0002 profile image
samar0002

Can you please explain what is issuer and realm here. Also about refreshing tokens?

Collapse
 
janstoltman profile image
Jan Stoltman

Hi, issuer and realm are just standard fields added to the claim set in JWT, you can find more about them on wikipedia en.wikipedia.org/wiki/JSON_Web_Token.

Refreshing token is a completely different story and can be implemented in many ways, I use short-lived access token + refresh token but it's up to your application really.