OKHttp Authenticator

Excellence is not enough!

OKHttp Authenticator

In this piece of article I’m gonna talk a little bit about OKHttp Authenticator and it’s use case.

Long story short the Authenticator class comes handy when the HTTP Request sent from Retrofit, encounters 401 error status code (UNAUTHORIZED: The request has not been applied because it lacks valid authentication credentials for the target resource). it means that you have to add a little something something to make your request an authorized one, that is usually an authentication token that you have already obtained from server and save somewhere around. using Authenticator you can handle this situation, whether sending a refreshed request or just letting the retrofit flow handle the situation that is normally throwing a destructive exception.
Most of the times 401 Unauthorized status code is returned because you need to refresh your authorization token you have added using a dedicated interceptor.

// You have added your authentication token in an interceptor.
class IAmAnAddTokenInterceptor(private val tokenSource: TokenSource): Interceptor(){
    override fun intercept(chain: Interceptor.Chain): Response{
        val originalRequest = chain.request()
        val requestBuilder = originalRequest.newBuilder()
            .header("Authorization", "Bearer  ${tokenSource.getToken()}")
        val request = requestBuilder.build()
        return chain.proceed(request)
    }
}

// And then you will add this interceptor to your HttpClient
  fun provideHttpClient(): OkHttpClient {
        val builder = OkHttpClient().newBuilder()
                .addInterceptor(IWasAnAddTokenInterceptor())
        return builder.build()

You have added the token and connect to server easily but for some reason which could be the token is expired and needs to be refreshed you get 401 error.
There is a legacy way of handling the situation using Interceptors that I’m gonna bring here although it’s deprecated but could be helpful to understand better and get the idea.

class IAmAnRefreshTokenInterceptor(private val tokenSource: TokenSource) : Interceptor() {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request().newBuilder()
                .addHeader("Authorization", "bearer ${tokenSource.getToken()}")
                .build()
        val response = chain.proceed(request)

        // when getting error response then continuing operation by refreshing the token 
        // and repeating the request with refreshed data
        if (response.code() == 401) {
            val newRequest = chain.request().newBuilder()
                    .addHeader("Authorization", "bearer ${tokenSource.refreshToken()}")
                    .build()
            return chain.proceed(newRequest)
        }

        return response
    }
}

So using the interceptor you can add token and at the same time when getting errors you can handle situation and refresh the token data with a new one.
but as you already know there is a more elegant and versatile way. so let’s jump into it.

class IAmASimpleAuthenticator(
        val tokenSource: TokenSource
) : Authenticator {
    override fun authenticate(route: Route?, response: Response?): Request? {
        lateinit var newRequest: Request
        try {
            newRequest = response.request().newBuilder()
                    .addHeader("Authorization", "bearer ${tokenSource.getToken()}")
                    .build()
            // returning new request with a refresh token
            return newRequest
        } catch (ex: Exception) {
        }
        // Returning null tells retrofit to catch the error itself without any further to do.
        return null
    }

}

In the above code (considering it’s a 401 response returned from server) we simply create a new request from the old one and replace the header field of “Authorization” or what ever field the server demands with a new data.

We can have a more advanced type of interceptor preventing an infinite loop of sending and receiving problematic requests. also maybe we have more than one way of authentication and we need to handle those in one place.


class RefreshAuthenticator(
        val tokenSource: TokenSource
) : Authenticator {

    override fun authenticate(route: Route?, response: Response?): Request? {
        // Did our request have a token?
        when (hasAuthorizationToken(response)) {
            false -> {
                // No auth token, we need other methods for authenticating so right now just returning to the app.
                // but we could implement the right method here in the production code.
                return null
            }
            true -> {
                val retryCount = response.request().header("RetryCount").toInt() ?: 0
                // Attempt to reauthenticate using the refresh token!
                if (retryCount > 10) {
                    // Don't try to re-authenticate any more, to prevent dead end loop.
                    return null
                }
                // Try for the new token:
                return response.request().newBuilder()
                        .header("Authorization", 
                                "bearer: ${tokenSource.refreshToken()}"
                        ).header("RetryCount",
                                "$retryCount"
                        ).build()

            }
        }
    }

    private fun hasAuthorizationToken(response: Response?): Boolean {
        response?.let { response ->
            return response.request().header("Authorization").startsWith("bearer: ")
        }
        return false
    }

}

In the above code I first checked the previous response to see if it has an authentication in it’s header. we can consider the empty header for authorization as there is other methods for authentication and handle it over there but for now I just neglected it.
then if we have authentication header it means that we need to refresh the token but first I checked the “RetryCount” header field that I’ve already added to the header in the previous calls to prevent a dead end loop of sending requests, here we can not send more than 10 times of a similar request.
and if anything goes well then we create new request and add a refreshed token also increase “RetryCount” for the next responses.

That’s it, now we are graduated from Authentication school! so we can handle 401 errors easily with that.
Hope you enjoy the article and if so feel free to like and comment and also re-share it.

Resources:
https://medium.com/knowing-android/headers-interceptors-and-authenticators-with-retrofit-1a00fed0d5eb
https://objectpartners.com/2018/06/08/okhttp-authenticator-selectively-reauthorizing-requests/