Handle refreshToken on both 401 & 403 in ktor-clilent android

51 views Asked by At

I am using ktor for my android app and its working fine now we have a new requirement that we need to handle the responseCode 401 or 403. For example if we hit getUsersAPI() call and it got 401 or 403 then we need to hit refreshToken API call and if refreshToken API call is successful then call failed API again with new tokens else logout user.

This is exactly my usecase but only thing is refreshToken should be called on 401 & 403.

  private fun httpClient() = HttpClient(engine = configuration.engine()) {
        configuration.requestRetry?.let { config ->
            val configuration: HttpRequestRetryConfig = HttpRequestRetryConfig().apply(config)

            install(HttpRequestRetry) {
                configuration.maxRetry?.let { maxRetries ->
                    val max = maxRetries.invoke()
                    /**
                    retryOnException: It will retry on all exception except cancellation exception
                     */
                    retryOnException(
                        maxRetries = max,
                        retryOnTimeout = configuration.retryOnTimeout.invoke()
                    )

                    /**
                    retry if status is not success: it will retry is status code is not [200, 300]
                     */
                    retryIf(maxRetries = max) { _, response ->
                        !response.status.isSuccess()
                    }
                }
                exponentialDelay()
                modifyRequest {
                    request.headers.append("x-retry-count", retryCount.toString())
                }
            }
        }
        install(ContentNegotiation) {
            json(
                Json {
                    ignoreUnknownKeys = true
                    encodeDefaults = true
                    isLenient = true
                    prettyPrint = true
                    explicitNulls = false
                },
            )
        }
        Logging {
            logger = object : Logger {
                override fun log(message: String) {
                    if (configuration.logging) {
                        httpMetrics.log(message)
                    }
                }
            }
            level = LogLevel.ALL
        }
        install(HttpTimeout) {
            requestTimeoutMillis = timeoutConfig.requestTimeoutMillis
            connectTimeoutMillis = timeoutConfig.connectTimeoutMillis
            socketTimeoutMillis = timeoutConfig.socketTimeoutMillis
        }
        defaultRequest {
            url {
                protocol = URLProtocol.createOrDefault(environment.protocol)
                host = environment.baseUrl
            }
            contentType(configuration.contentType)
        }
        HttpResponseValidator {
            @Suppress("DEPRECATION")
            expectSuccess = [email protected]
            validateResponse { response ->

                val statusCode = response.status.value
                val originCall = response.call
                if (statusCode == 204 /*|| statusCode == 201*/) throw AppException(
                    ErrorResponse(
                        statusCode.toString(),
                        NO_DATA_FOUND
                    )
                )
                if (statusCode < 300 || originCall.attributes.contains(ValidateMark)) {
                    return@validateResponse
                }

                if (statusCode == 401 || statusCode == 403) {
                    println("HANDLE ERROR HERE FOR REFFRESS------------------------------------>>")
                }
                val exceptionCall = originCall.save().apply {
                    attributes.put(ValidateMark, Unit)
                }

                val exceptionResponse = exceptionCall.response
                val exceptionResponseText = try {
                    exceptionResponse.bodyAsText()
                } catch (_: MalformedInputException) {
                    BODY_FAILED_DECODING
                }
                print(exceptionResponseText)
                val errorResponse = Json.decodeFromString<ErrorResponse>(exceptionResponseText)
                throw AppException(errorResponse)
            }
        }

        install(Auth) {
            bearer {
                sendWithoutRequest { request ->
                    request.url.host == "www.googleapis.com"
                }
                loadTokens {
                    val token = authToken().accessToken.orEmpty()
                    val refreshToken = authToken().refreshToken.orEmpty()
                    BearerTokens(accessToken = token, refreshToken = refreshToken)
                }
                refreshTokens {
                    val workEmail = authToken().workEmail.orEmpty()
                    val refreshToken = authToken().refreshToken.orEmpty()
                    val response = client.post {
                        markAsRefreshTokenRequest()
                        this.apply(
                            RefreshToken(
                                refreshTokenId = "",
                                refreshToken = refreshToken,
                                email = workEmail,
                                deviceId = deviceId!!
                            )
                        )
                    }
                    if (response.status == HttpStatusCode.Unauthorized || response.status == HttpStatusCode.Forbidden) {
                        println("application will logout")
                        null
                    } else {
                        println("application in else part")
                        val ktorLoginResponse = response.body<RefreshTokenModel>()

                        ktorLoginResponse.data?.let { ktorAccessToken ->
                            ktorAccessToken.token?.let { accessToken ->
                                ktorAccessToken.refreshToken?.let { refreshToken ->
                                    BearerTokens(accessToken, refreshToken)
                                }
                            }
                        }
                    }
                }
            }
        }
    }

How can I get this working. PLease suggest me what I am doing wrong

0

There are 0 answers