OkHttp Authenticator looping after getting new tioken request

382 views Asked by At

I'm making a basic application to hit Twitch servers that use short-lived tokens for the APIs. So I've made an okhttp3.Authenticator class with Retrofit and Hilt for handling the adding of tokens to authenticate very similar to several others I've seen in tutorials and even here in SO. The usual when it 401s, fetches a new token and adds it to the response in a newRequest header but for some reason it keeps looping at that stage and breaks with too many requests error.

It is a very similar issue to this question but even after implementing the accepted solution it keeps looping.

Any idea as to what could be the issue?

My Authenticator:

class AuthAuthenticator @Inject constructor(
    private val sessionManager: SessionManager,
    private val authApi: AuthApi
) : Authenticator {


    override fun authenticate(route: Route?, response: Response): Request? {
        return runBlocking {

        // call login api again for getting accessToken
        val apiResponse = getNewToken()

        if (apiResponse.isSuccessful) {
            val accessToken = apiResponse.body()?.token ?: return@runBlocking null                
            val expireTime = apiResponse.body()?.expireTime ?: return@runBlocking null
            accessToken.let {                    
                sessionManager.saveToken(accessToken, expireTime)
            }                
            response
                    .request                    
                    .newBuilder()
                    .header("Authorization", "Bearer $accessToken")
                    .build()
            } else {
                null            
            }
        }    
    }

    private suspend fun getNewToken(): retrofit2.Response<TokenResponse> {
        return authApi.getAuthToken(
            clientId = ExternalInfoHelper.valuesMap()[ApiValues.clientIdKey] ?: "",
            clientSecret = ExternalInfoHelper.valuesMap()[ApiValues.clientSecretKey] ?: "",
            grantType = ExternalInfoHelper.valuesMap()[ApiValues.grantTypeKey] ?: ""        )
    }
}


And I initialize my okhttp client like this, using some DI for different retrofit clients since different URLs:

private fun createOkHttpClient(
    authInterceptor: AuthInterceptor?,
    authAuthenticator: AuthAuthenticator?
): OkHttpClient {
    val loggerInterceptor = HttpLoggingInterceptor()
    loggerInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
    val okhttp = OkHttpClient.Builder()
        .addInterceptor(loggerInterceptor)

    authAuthenticator?.let {        
        okhttp.authenticator(it)
    }    
    authInterceptor?.let {        
        okhttp.addInterceptor(it)
    }    return okhttp.build()
}


This is my Interceptor for adding the stored token:

class AuthInterceptor @Inject constructor(private val sessionManager: SessionManager) :
    Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val token = runBlocking {            
            sessionManager.getToken().first()
        }        
        val request = chain.request().newBuilder()
        request.addHeader(ApiValues.authHeaderKey, ApiValues.authHeaderValue.plus(token))
        return chain.proceed(request.build())
    }
}


I have tried re-writing the code in several different ways, returning null and that doesn't change the looping. Returning the request with a hard-coded value I got from Postman goes into too many requests error in the logs without actually doing any requests

1

There are 1 answers

1
Nebrass wahaib On

First, verify that the AuthAuthenticator class is properly injected and provided by Hilt. Make sure the dependencies SessionManager and AuthApi are correctly injected and available.

Next, let's take a closer look at the authenticate function in your

AuthAuthenticator class:

override fun authenticate(route: Route?, response: Response): Request? {
    return runBlocking {
        // call login api again for getting accessToken
        val apiResponse = getNewToken()

        if (apiResponse.isSuccessful) {
            // ... code to save and set the new access token ...

            response
                .request                    
                .newBuilder()
                .header("Authorization", "Bearer $accessToken")
                .build()
        } else {
            null            
        }
    }    
}

Based on the code snippet you provided, it seems like you're missing the assignment of the updated request object. The response.request.newBuilder() method creates a new request builder, but you need to assign it back to response.request in order to update the request with the new access token header. Modify the code as follows:

override fun authenticate(route: Route?, response: Response): Request? {
    return runBlocking {
        // call login api again for getting accessToken
        val apiResponse = getNewToken()

        if (apiResponse.isSuccessful) {
            // ... code to save and set the new access token ...

            response
                .request
                .newBuilder()
                .header("Authorization", "Bearer $accessToken")
                .build()
        } else {
            null            
        }
    }    
}