In my mobile app, I have a singleton Ktor HttpClient
with bearer authentication configured as such:
HttpClient(…) {
install(Auth) {
bearer {
sendWithoutRequest { requestBuilder -> some condition }
loadTokens(tokensStorage::getTokens)
refreshTokens {
performTokenRefresh(oldTokens.refreshToken)
}
}
}
}
Now consider the following flow:
- The user opens the app while the tokens stored in my
tokenStorage
are not valid. - The
HttpClient
loads the tokens fromtokenStorage
and the request fails with a 401. - The
HttpClient
tries to perform a refresh but it fails too because the refresh token is invalid. - The user is redirected to the login page, logs in, and the new valid tokens are stored in
tokenStorage
. - Now from the app the user can retry the call that failed in points 2-3. However this will fail forever until the app is closed, because the
HttpClient
never tries to callloadTokens
anymore. Indeed, as far as I can see from the source code,loadTokens
is called only once and then never again.
I found out a couple of ways to solve the issue.
The first one is to manually retrieve BearerAuthProvider
from the HttpClient
and clear the token myself like in the following snippet, but it seems like a hacky workaround:
httpClient.plugin(Auth).providers
.filterIsInstance<BearerAuthProvider>()
.first().clearToken()
Another one is to manually load the current token from my tokenStorage
in refreshToken
and disregard what get passed in this@refreshTokens.oldTokens
:
refreshTokens {
val currentRefreshToken = tokenStorage.getTokens().refreshToken
performTokenRefresh(currentRefreshToken)
}
However this means that the client will do an unnecessary call to the refresh API, while having already a valid token pair (obtained from the login).
So my question is: is there a cleaner way to handle the situation? Am I misusing Ktor?