loading...

Ktor - Setting up JWT

janstoltman profile image Jan Stoltman ・2 min read

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"
 install(Authentication) { jwt { } }

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"
}

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

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) }

        }
}

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()

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
    }

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!")
            }
        }

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

Posted on by:

janstoltman profile

Jan Stoltman

@janstoltman

Kotlin multiplatform developer with Android development background

Discussion

pic
Editor guide
 

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?

 

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