DEV Community

Cover image for Authenticator in Retrofit Android

Authenticator in Retrofit Android

Mohit Rajput on November 27, 2019

There is a general use-case in any application that the authentication token expires, and you need to refresh the internally. After upgrading the t...
Collapse
 
mohitrajput987 profile image
Mohit Rajput

Many folks requested me to write a good implementation of TokenAuthenticator class i.e. how to refresh token.
Soon I will publish a new article on how I refresh tokens in authenticator.

Collapse
 
ashubuntu profile image
ashubuntu

if requestParams potentially consists of username and password, how do you provide that on the fly especially when the primary functionality of the app is to log the user out to the login activity when token is expired. By the way, this technique surely informs about 401 code.

Collapse
 
mohitrajput987 profile image
Mohit Rajput

Ideally we should have a refreshToken() function instead of login again.
But if you want to login again, then you can display a modal to user to enter username and password then resume this functionality as per given in blog.

Collapse
 
adriyoutomo profile image
Adri

nice article, i think i should use this in my next project 😁

Collapse
 
mohitrajput987 profile image
Mohit Rajput

Thanks Adri. Sure we should use this.

Collapse
 
arieftb profile image
Arief Turbagus (TB)

Can I use this solution for parallel request API?

Collapse
 
mohitrajput987 profile image
Mohit Rajput • Edited

Ofcourse. Make sure you manage authenticate() method accordingly.
E.g. make getUpdatedToken() call synchronize

Collapse
 
ricindigus profile image
RICARDO MORALES

Awesome, thank you very much!!!!!!!

i need this for my work.
You saved me.

Collapse
 
mohitrajput987 profile image
Mohit Rajput

Thanks @ricardo

Collapse
 
ayoub_anbara profile image
ayoub anbara 🇲🇦

If refresh token is expired also, and request refresh token return 401, how to handle this case?

Collapse
 
mohitrajput987 profile image
Mohit Rajput • Edited

Here is the complete code which I used in one project:

package one.projectname.sdk.network

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route

/**
 * Created by Mohit Rajput on 09/03/22.
 */
class TokenAuthenticator(
    private val tokenManager: TokenManager
) : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request {
        synchronized(this) {
            val sessionData = if (isRefreshNeeded(response)) {
                runBlocking { getUpdatedSessionData() }
            } else {
                getExistingSessionData()
            }

            return response.request.newBuilder()
                .header(HeaderKeys.SESSION_ID, sessionData.sessionId)
                .header(HeaderKeys.REFRESH_ID, sessionData.refreshId)
                .build()
        }
    }

    private fun isRefreshNeeded(response: Response): Boolean {
        val oldSessionId = response.request.header(HeaderKeys.SESSION_ID)
        val oldRefreshId = response.request.header(HeaderKeys.REFRESH_ID)

        val updatedSessionId = tokenManager.getSessionId()
        val updatedRefreshId = tokenManager.getRefreshId()

        return (oldSessionId == updatedSessionId && oldRefreshId == updatedRefreshId)
    }

    private fun getExistingSessionData(): ApiResponse.SessionData {
        val updatedSessionId = tokenManager.getSessionId()
        val updatedRefreshId = tokenManager.getRefreshId()
        return ApiResponse.SessionData(
            sessionId = updatedSessionId,
            refreshId = updatedRefreshId
        )
    }

    private suspend fun getUpdatedSessionData(): ApiResponse.SessionData {
        val refreshTokenRequest =
            ApiResponse.RefreshSessionRequest(tokenManager.getRefreshId())
        return when (val result =
            getResult { userApiService().refreshSession(refreshTokenRequest) }) {
            is ApiResult.Success -> {
                val sessionData = result.data.data
                tokenManager.saveSessionId(sessionData.sessionId)
                tokenManager.saveRefreshId(sessionData.refreshId)
                delay(50)
                sessionData
            }
            is ApiResult.Error -> {
                MySdk.instance().mySdkListeners?.onSessionExpired()
                return ApiResponse.SessionData()
            }
        }
    }

    private class CustomNetworkStateChecker : NetworkStateChecker {
        override fun isNetworkAvailable() = true
    }

    private fun userApiService(): UserApiService {
        val retrofit = RetrofitHelper.provideRetrofit(
            RetrofitHelper.provideOkHttpClient(CustomNetworkStateChecker(), tokenManager)
        )
        return retrofit.create(UserApiService::class.java)
    }
}
Enter fullscreen mode Exit fullscreen mode