I have a KMM project in which I have been using Ktor for the API calls. I have a requirement in which I need to update my access token with the help of refresh tokens if they are expired. Basically I just need to add an authentication module in my Ktor client. No I have gone through all Ktor documentation and added Auth
module in my KMM.
Now when I add auth module in my http client it gets added successfully and whenever I receive UnAuthorized
user error from any API it calls my refresh token API. The issue is even though it calls my refresh token API but on success of refresh token it does not call the other API from which I have received UnAuthorized
user error.
It works as expected in Android but the only issue is in iOS client.
Expected (Works fine in Android Http client) :-
UnAuthorized
user error call refresh token API -> onSuccess of refresh token API -> Call the first API again with the updated refresh token.Issue I am facing :-
UnAuthorized
user error call refresh token API -> onSuccess of refresh token API -> Does nothing on this step just throws unauthorized user error from the first API.HttpClient
for iOS :-
actual class HttpBaseClient {
actual val tokenClient = HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
url {
protocol = URLProtocol.HTTPS
}
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
install(JsonFeature) {
val json = kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
serializer = KotlinxSerializer(json)
}
}
actual val httpClient: HttpClient = HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
url {
protocol = URLProtocol.HTTPS
}
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
// Validate Response
expectSuccess = false
// Install Auth
install(Auth) {
lateinit var refreshTokenInfo : LoginResponse
bearer {
refreshTokens { unauthorizedResponse: HttpResponse ->
NSLog("Unauthorized response received")
BaseAPIClass().refreshAuthToken().fold(
failed = {
// On Failed
NSLog("Token Failed"). // No Callback received here
},
succeeded = { response ->
refreshTokenInfo = response
NSLog("Token Updated") // No Callback received here even when API is success
}
)
BearerTokens(
accessToken = refreshTokenInfo.accessToken ?: "",
refreshToken = refreshTokenInfo.refreshToken ?: ""
)
}
}
}
// JSON Deserializer
install(JsonFeature) {
val json = kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
serializer = KotlinxSerializer(json)
}
Android Client (Pretty much same):-
actual class HttpBaseClient {
actual val tokenClient = HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
url {
protocol = URLProtocol.HTTPS
}
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
install(JsonFeature) {
val json = kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
serializer = KotlinxSerializer(json)
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
actual val httpClient: HttpClient = HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
url {
protocol = URLProtocol.HTTPS
}
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
// Validate Response
expectSuccess = false
//Authentication
install(Auth) {
lateinit var refreshTokenInfo : LoginResponse
bearer {
refreshTokens { unauthorizedResponse: HttpResponse ->
BaseAPIClass().refreshAuthToken().fold(
failed = {
// On Failed
},
succeeded = { response ->
refreshTokenInfo = response
}
)
BearerTokens(
accessToken = refreshTokenInfo.accessToken ?: "",
refreshToken = refreshTokenInfo.refreshToken ?: ""
)
}
}
}
Ktor version :- 1.6.2 (tried 1.6.4 as well after reading this issue but didn't work)
I got this working only the error is at this code :-
succeeded = { response ->
refreshTokenInfo = response
NSLog("Token Updated") // No Callback received here even when API is success
}
I am not sure why but assigning the response to my lateinit var refreshTokenInfo
is causing the main problem here. I removed that and updated my code to
refreshTokens { unauthorizedResponse: HttpResponse ->
BaseAPIClass().refreshAuthToken().fold(
failed = {
// On Failed
return@refreshTokens BearerTokens(
accessToken = "",
refreshToken = ""
)
},
succeeded = { response ->
return@refreshTokens BearerTokens(
accessToken = response.accessToken ?: "",
refreshToken = response.refreshToken ?: ""
)
}
)
}
and this works.
I have also raised the issue here you can go through the details.