Okhttp 401 handler

Handle session expirations and 401 error codes. The following code will be activated if the API responds with a 401 (no authentication). If there’s a token that should be renewed, we first renew the token and if this is successful we try the original request again.

This recipe assumes:

  • Kotlin, with Koin dependency injection
  • Retrofit + OkHttp
  • The presence of a Preferences class that is backed by SharedPreferences, containing the access token and refresh token.

TokenAuthenticator class

class TokenAuthenticator : Authenticator, KoinComponent {
    private val prefs: Preferences by inject()
    private val authStatus: ForcedLogoutLiveData by inject()

    override fun authenticate(route: Route?, response: Response): Request? {
        synchronized(this) {
            return if (response.code == 401) {
                val authToken = prefs.accessToken

                // If there're no access or refresh tokens or we already tried, give up
                return if (authToken.isNullOrBlank() || prefs.refreshToken.isNullOrBlank() || response.request.header(
                        "Authorization"
                    ) != null
                ) {
                    null
                } else {

                    // Refresh the token
                    val refreshCall =
                        Api.tokenService.refreshSync(TokenRefreshRequest(prefs.refreshToken!!))
                    
                    // Make it a synchronous call
                    val refreshResponse = refreshCall.execute()
                    if (refreshResponse.code() == 200 && refreshResponse.body()?.access != null) {

                        // Customize this to the response format of your API
                        prefs.accessToken = refreshResponse.body()!!.access
                        authStatus.forceLogout(false)
                        response.request.newBuilder()
                            .header(
                                "Authorization",
                                "Bearer ${refreshResponse.body()?.access}"
                            )
                            .build()
                    } else {
                        // No access token? Force a logout
                        prefs.accessToken = ""
                        authStatus.forceLogout(true)
                        null
                    }
                }
            } else {
                null
            }
        }
    }
}

Usage

    private val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(httpLoggingInterceptor)
        .addInterceptor(authorizationHeaderInterceptor)
        .authenticator(TokenAuthenticator())
        .build()