Hi Ktor Users! I'm currently using Ktor Server tog...
# ktor
f
Hi Ktor Users! I'm currently using Ktor Server together with Ktor Client. I'm not sure how to handle my current flow in a DRY manner. When an user sends a request to my server they have an Auth header. Via that jwt token Im sending a request to another server to gain an API key, that I can use when sending requests to a third party service. This API key needs to be send via a header, that I append on the client. My question is: How can I re use this flow instead of writing it in every single Route?
a
Can you share your code from the route for the illustration?
f
Sure of course. I've removed boilerplate to make it more readable. The important parts is kept in. I'm using Koin as DI. Request example:
Copy code
fun Route.exampleRoute() {
    route("users/apikey") {
        exampleRouteGet()
    }
}

fun Route.exampleRouteGet() {
    val userService: UserService by inject<UserService>()
    val client: HttpClient by inject<HttpClient>()

    get("") {
        val principal = call.principal<JWTPrincipal>()
        checkNotNull(principal) { "Principal is null" }
        val userId: String = principal.payload.getClaim("userId").asString()
        val token = call.request.parseAuthorizationHeader().toString().removePrefix("Bearer ")


        val apiKey = userService.findApiKey(userId, token)

        val response = client.get("someurlexample.com") {
            headers.append("X-Api-Key", apiKey)
        }

        /**
         * Send result back to frontend
         */
    }
}
Service layer:
Copy code
class UserService(private val userCache: UserCache, private val httpClient: HttpClient) {


    suspend fun findApiKey(userId: String, token: String): String {
        val apiKey = userCache.readUserApiCache(userId)
        if (apiKey != null) return apiKey

        val response = httpClient.get("someotherapi.com/apikey") {
            bearerAuth(token)
        }
        
        /**
         * Handle result and store in cache
         */
        
        return apiKey
    }

}
a
Hi @Filip Andersen! I’m looking forward to knowing more about this, we got exactly the same issue in our business app. I tried several solutions but in the end, given up by passing it each time. We started using Ktorfit autogenerated client, and the headers are specified as annotated parameters of the methods so…. BUT some teams in my company hacked this a little and actually created a custom CoroutineContext where they store these kind of params to retrieve them later in the call chain from the said context. You also have the possibility to use context-receivers (deprecated but upcoming context parameters will solve this too). I experimented a little and it seemed to work well. The call to you client being in the context of an ApplicationCall, you can access the headers from it without “polluting” the method signature (it is, actually but you don’t see it).
Copy code
context(ApplicationCall)
class UserService(private val userCache: UserCache, private val httpClient: HttpClient) {


    suspend fun findApiKey(userId: String): String {
        val apiKey = userCache.readUserApiCache(userId)
        if (apiKey != null) return apiKey

        val response = httpClient.get("someotherapi.com/apikey") {
            // here you can access the request from the call
            bearerAuth(request.authorization())
        }
        
        /**
         * Handle result and store in cache
         */
        
        return apiKey
    }

}
Also, notice the helper
request.authorization()
from the call instance. This helps to retrieve the bearer easily Given you’re using Koin, you may have to annotate the method instead of the entire class with the
context()
. I don’t know if it would work that way.
f
Hi @Anthony Legay! Very interesting. Thanks a lot for your insight 😄 You've given me some ideas to try out. I will experiment a little bit when time allows me to! To be honest I was shocked to find out how "hard" this is to achieve, it seems as a pretty standard business case to me. Maybe Ktor will have an out of the box solution for this one day!