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 bySharedPreferences
, 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()