Search code examples
kotlinbearer-tokenktorktor-client

Ktor Client -- Bearer Token never refreshed


I'm trying to handle authentication with bearer tokens with Ktor but after the access token gets invalidated refreshTokens { ... } is never triggered. This is my service:

interface MyService {
    companion object Factory {
        fun build(getToken: GetToken, refreshToken: RefreshToken): MyService {
            return MyServiceImpl(httpClient = HttpClient(CIO) {
                install(JsonFeature) {
                    serializer = KotlinxSerializer(
                        kotlinx.serialization.json.Json {
                            ignoreUnknownKeys = true
                        }
                    )
                }
                install(Logging) {
                    logger = Logger.DEFAULT
                    level = LogLevel.HEADERS
                }
                install(Auth) {
                    lateinit var tokenInfo: TokenInfo
                    lateinit var reloadTokenInfo: TokenInfo

                    bearer {
                        refreshTokens {
                            Timber.d("token refresh")
                            val refresh = refreshToken.execute()
                            BearerTokens(
                                accessToken = refresh.accessToken,
                                refreshToken = refresh.refreshToken
                            )
                        }
                        loadTokens {
                            Timber.d("token loading")
                            val tokenInfo = getToken.execute()
                            BearerTokens(
                                accessToken = tokenInfo.accessToken,
                                refreshToken = tokenInfo.refreshToken
                            )
                        }
                    }
                }
            })
        }
    }
}

Ktor Log:

I/System.out: 16:25:59.167 [main] INFO io.ktor.client.HttpClient - REQUEST ...
I/System.out: 16:25:59.168 [main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
I/System.out: 16:25:59.168 [main] INFO io.ktor.client.HttpClient - COMMON HEADERS
I/System.out: 16:25:59.168 [main] INFO io.ktor.client.HttpClient - -> Accept: application/json
I/System.out: 16:25:59.169 [main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
I/System.out: 16:25:59.169 [main] INFO io.ktor.client.HttpClient - -> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYxMTYyNTI3LTI3NzEtNDIyOS05YzUwLWE1NDk3MmIxMzZhYSIs
I/System.out: 16:25:59.169 [main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
I/System.out: 16:25:59.169 [main] INFO io.ktor.client.HttpClient - -> Content-Length: 0
I/System.out: 16:25:59.262 [main] INFO io.ktor.client.HttpClient - RESPONSE: 401 Unauthorized
I/System.out: 16:25:59.262 [main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
I/System.out: 16:25:59.262 [main] INFO io.ktor.client.HttpClient - FROM: ...
I/System.out: 16:25:59.263 [main] INFO io.ktor.client.HttpClient - COMMON HEADERS
I/System.out: 16:25:59.263 [main] INFO io.ktor.client.HttpClient - -> Access-Control-Allow-Credentials: true
I/System.out: 16:25:59.263 [main] INFO io.ktor.client.HttpClient - -> Access-Control-Allow-Origin: *
I/System.out: 16:25:59.263 [main] INFO io.ktor.client.HttpClient - -> Content-Length: 85
I/System.out: 16:25:59.264 [main] INFO io.ktor.client.HttpClient - -> Content-Type: application/json; charset=utf-8
I/System.out: 16:25:59.265 [main] INFO io.ktor.client.HttpClient - -> Date: Mon, 14 Feb 2022 16:25:59 GMT
I/System.out: 16:25:59.266 [main] INFO io.ktor.client.HttpClient - -> Server: Apache/2.4.29 (Ubuntu)
I/System.out: 16:25:59.266 [main] INFO io.ktor.client.HttpClient - -> X-Content-Type-Options: nosniff
I/System.out: 16:25:59.266 [main] INFO io.ktor.client.HttpClient - -> X-Powered-By: Express
I/System.out: 16:25:59.267 [main] INFO io.ktor.client.HttpClient - -> www-authenticate: Bearer

I found various posts where it says that if the server responds with code 401 and the www-authenticate header, Ktor will figure out that the tokens need to be refreshed and do it automagically. However, the refreshTokens block is never triggered.

Anybody got any ideas why that might be the case? I'd appreciate any help :)


Solution

  • There are multiple reasons for not calling the refreshTokens callback:

    • Response status isn't 401 Unauthorized
    • Attributes of a request contain circuitBreaker (this means a refresh token is already requested)
    • No WWW-Authenticate header is present
    • The WWW-Authenticate header value is malformed
    • No auth provider is found by an auth scheme and a realm (parsed from the WWW-Authenticate header value)

    According to the log in the question's description, the refreshTokens callback should be called because all the above conditions are met.