Praveen Rawat
Praveen Rawat

Reputation: 324

Call Api Again After Token Refresh

I am using Authenticator instead of Interceptor to refresh the token. I am able to detect the 401 exception and easily refresh the new token. Everything is working perfectly but the issue is following:

I am unable to call the request again, I do not want the user to hit again to call the offer.

So after execution of the code below I get a new token, it gives me a 401 error message. My Question is: How can I call the request chain again? Any advice on the implementation is welcome.

class OffersViewModel

    val observable = ApiServiceClient.createApiUsingToken(context).getOffers(
                                Pref.getString(getApplication(), Pref.CUSTOMER_CODE, "")!!,
                                Pref.getString(getApplication(), Pref.TOKEN, "")!!
                        )
                        compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                                .observeOn(AndroidSchedulers.mainThread())
                                .doOnSubscribe {
                                    responseModel.statusCode = StatusCode.START
                                    offersRegisteredUserResponseLiveData.postValue(responseModel)
                                }
                                .subscribe({ success ->
                                    if (success.errors.isNullOrEmpty()) {
                                        success.statusCode = StatusCode.SUCCESS
                                    } else {
                                        success.statusCode = StatusCode.ERROR
                                    }
                                    offersRegisteredUserResponseLiveData.value = success
                                }, {
//HERE I GOT 401
                                    Log.d("debug",it.message.toString())
                                    responseModel.statusCode = StatusCode.ERROR
                                    offersRegisteredUserResponseLiveData.value = responseModel
                                }, { })
                        )

API Service Class

/*.....Offer Screen...........*/
    @GET("offers/xyz/{abc}")
    fun getOffers(
            @Path("abc") customerCode: String,
            @Header("Authorization") authorization: String,
            @Header("Content-Type") contentType: String = CONTENT_TYPE
    ):
            Observable<OfferRegisteredUserResponseModel>

ApiClient Class

fun createApiUsingToken(context: Context?): ApiService {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY
            val client = OkHttpClient.Builder().addInterceptor(interceptor).connectTimeout(20, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(60, TimeUnit.SECONDS)
                    .authenticator(TokenInterceptor(context)).build()


            val retrofit = Retrofit.Builder()
                    .client(client)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(Constants.BASE_URL)
                    .build()
            var ApiServiceClient=retrofit.create(ApiService::class.java)
            return retrofit.create(ApiService::class.java)
        }

class TokenInterceptor

var requestAvailable: Request? = null
        if (response!!.code() === 401) {
            var retrofitResponse = ApiServiceClient.createToken().getTokenWithoutObserver().execute()
            if (retrofitResponse != null) {
                val refreshTokenResponse = retrofitResponse!!.body()
                val newAccessToken = refreshTokenResponse!!.token
                if (newAccessToken != null)
                {
                    Pref.setString(MyApplication.mInstance, Pref.TOKEN, "${refreshTokenResponse.tokenType} ${refreshTokenResponse?.token}")
                    Pref.setString(MyApplication.mInstance, Pref.TOKEN_EXPIRES_IN, refreshTokenResponse.tokenExpirationTime.toString())
                    Utils.addTokenExpirationTimeToCurrentTime(MyApplication.mInstance, refreshTokenResponse.tokenExpirationTime?.toInt()!!)

                    try {
                        requestAvailable = response?.request()?.newBuilder()
                                ?.addHeader("Content-Type", "application/json")
                                ?.addHeader("Authorization", "Bearer " + newAccessToken)
                                ?.build()
                        return requestAvailable
                    } catch (ex: Exception) {
                    }
            }
            } else
                return null
        }
        return requestAvailable

Upvotes: 0

Views: 658

Answers (1)

Alex
Alex

Reputation: 972

Couple of things i see wrong with this.

First is that even if you "restart" the request with the new token, if you happen to make another request while the "new token" is not saved, that request is also going to fail.

Second is that i don't see that you save the new token anywhere (in SharedPrefs for example for later use).

This is how i would have do it: (preferenceHelper is SharedPrefs)

override fun authenticate(route: Route?, response: Response): Request? {
    val HEADER_AUTHORIZATION = "Authorization"
    // We need to have a token in order to refresh it.
    val token = preferenceHelper.getAccessToken() ?: return null

    synchronized(this) {
        val newToken = preferenceHelper.getAccessToken() ?: return null

        // Check if the request made was previously made as an authenticated request.
        if (response.request().header(HEADER_AUTHORIZATION) != null) {

            // If the token has changed since the request was made, use the new token.
            if (newToken != token) {
                return response.request()
                    .newBuilder()
                    .removeHeader(HEADER_AUTHORIZATION)
                    .addHeader(HEADER_AUTHORIZATION, "Bearer " + newToken)
                    .build()
            }
            val tokenResponse = ApiServiceClient.createToken().getTokenWithoutObserver().execute()

            if (tokenResponse.isSuccessful) {

                val userToken = tokenResponse.body() ?: return null

                preferenceHelper.saveAccessToken(userToken.token)
                preferenceHelper.saveRefreshToken(userToken.refreshToken)

                // Retry the request with the new token.
                return response.request()
                    .newBuilder()
                    .removeHeader(HEADER_AUTHORIZATION)
                    .addHeader(HEADER_AUTHORIZATION, "Bearer " + userToken.token)
                    .build()
            } else {
                logoutUser()
            }
        }
    }
    return null
}

Upvotes: 1

Related Questions