Authenticate api if accessToken expired in retrofit api client in android [kotlin].

Mehedi Hassan Piash
3 min readNov 19, 2022

Expiring access token for authenticating api is a common case in mobile app development when implementing any api in our app. In this case, we have to update the access token called the refresh token to access the api further.

As we use retrofit and okHttp in our android application for calling api, there are two interfaces in OkHttp to handle this type of situation one is called Interceptor for sending the access token in the header and another is called Authenticator for fetching refresh token by calling api.

Let's do the implementation step by step.

Gradle dependency

// retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
/**
* Interceptor to add auth token to requests
*/
class AuthInterceptor(context: Context) : Interceptor {
private val sessionManager = SessionManager(context)

override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
sessionManager.fetchAuthToken()?.let {
requestBuilder.addHeader(
"Authorization", "Bearer $it"
)
}
return chain.proceed(requestBuilder.build())
}
}
/**
* Authenticator to get the RefreshToken if current token is expired
*/
class AuthenticateApi(context: Context) : Authenticator {
private val sessionManager = SessionManager(context)

override fun authenticate(route: Route?, response: Response): Request? {
val apiClient = Retrofit.Builder().baseUrl(ApiUrl.BASE_URL) // api base url
.addConverterFactory(GsonConverterFactory.create()).build()
.create(ApiService::class.java)
return runBlocking {
// call login api again for getting accessToken in runBlocking so that it will wait until api response come
val apiResponse = apiClient.login(LoginBody("piash599@gmail.com", "p1234", "user"))
if (apiResponse.isSuccess) {
val accessToken = apiResponse.data?.accessToken
accessToken?.let {
sessionManager.saveAuthToken(accessToken)
}
response.request.newBuilder().addHeader(
"Authorization", "Bearer $accessToken"
).build()
} else {
null
}
}
}
}
interface ApiService {
@POST(ApiUrl.USER_LOGIN)
suspend fun login(@Body loginBody: LoginBody): BaseModel<LoginResponse>
}

Here are the Retrofit client and OkHttp [Used hilt as a dependency injection]

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
/**
* Provides BaseUrl as string
*/
@Singleton
@Provides
fun provideBaseURL(): String {
return ApiUrl.BASE_URL
}

/**
* Provides LoggingInterceptor for api information
*/
@Singleton
@Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}

/**
* Provides Auth interceptor for access token
*/
@Singleton
@Provides
fun provideAuthInterceptor(@ApplicationContext context: Context): AuthInterceptor {
return AuthInterceptor(context)
}

/**
* Provides AuthenticateApi to check api is authenticate or not
*/
@Singleton
@Provides
fun provideAuthenticateApi(@ApplicationContext context: Context): AuthenticateApi {
return AuthenticateApi(context)
}

/**
* Provides custom OkkHttp
*/
@Singleton
@Provides
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
authInterceptor: AuthInterceptor,
authenticateApi: AuthenticateApi
): OkHttpClient {
val okHttpClient = OkHttpClient().newBuilder()

okHttpClient.callTimeout(40, TimeUnit.SECONDS)
okHttpClient.connectTimeout(40, TimeUnit.SECONDS)
okHttpClient.readTimeout(40, TimeUnit.SECONDS)
okHttpClient.writeTimeout(40, TimeUnit.SECONDS)
okHttpClient.addInterceptor(loggingInterceptor)
okHttpClient.addInterceptor(authInterceptor)
okHttpClient.authenticator(authenticateApi)
okHttpClient.build()
return okHttpClient.build()
}
/**
* Provides converter factory for retrofit
*/
@Singleton
@Provides
fun provideConverterFactory(): Converter.Factory {
return GsonConverterFactory.create()
}

/**
* Provides ApiServices client for Retrofit
*/
@Singleton
@Provides
fun provideRetrofitClient(
baseUrl: String, okHttpClient: OkHttpClient, converterFactory: Converter.Factory
): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(converterFactory)
.build()
}
/**
* Provides Api Service using retrofit
*/
@Singleton
@Provides
fun provideRestApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}

}

SessionManger to store authToken using SharedPreference

/**
* Session manager to save and fetch data from SharedPreferences
*/
class SessionManager (context: Context) {
private var prefs: SharedPreferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE)

companion object {
const val USER_TOKEN = "user_token"
}

/**
* Function to save auth token
*/
fun saveAuthToken(token: String) {
val editor = prefs.edit()
editor.putString(USER_TOKEN, token)
editor.apply()
}

/**
* Function to fetch auth token
*/
fun fetchAuthToken(): String? {
return prefs.getString(USER_TOKEN, null)
}

/**
* Function to fetch auth token
*/
fun clearAuthToken() {
prefs.edit().clear().commit()
}
}

Ref: https://piashcse.blogspot.com/2022/11/authenticate-api-if-accesstoken-expired.html

--

--

Mehedi Hassan Piash

Senior Software Engineer @Field Nation | Android | Kotlin | Compose | Kmp | React-Native