There is a general use-case in any application that the authentication token expires, and you need to refresh the internally. After upgrading the token, the API call should be executed again, and UI should be updated. Let's understand this scenario by an example:
- You are building a social network application which has various functionalities, i.e. new feeds, friend requests list, send a friend request, see comments on a post etc.
- After login, you provide an authentication token(i.e. JWT token) to the user. This token has an expiry time of 10 mins(which could be dynamic at server-side), and this should be passed in the header of each API call.
- When you go to the friend requests list screen, you get
401 Unauthorized
which means you need to refresh the token. - You not only need to update the token but also make the call to friend requests list API again and show the list in UI.
The naive way of handling this is, on each API call, make a check of 401 status code and call the refresh token API again. After calling the API, make the call to required API again. This code needs to be written everywhere(i.e. in each API call callback).
Does Android(more specific, Retrofit) have any way to handle this scenario using a standard code?
The answer is YES!!! We have a way of handling this scenario in Retrofit
using Authenticator
.
You can achieve this functionality using simple steps which are described below:
1. Method to fetch updated token
Declare a method in the retrofit interface to fetch the updated token from the server, as shown below:
interface UserApiService {
companion object {
private const val REQUEST_REFRESH_TOKEN = "/oauth/token"
}
@FormUrlEncoded
@POST(REQUEST_REFRESH_TOKEN)
fun getAuthenticationToken(@FieldMap params: HashMap<String, String>): retrofit2.Call<AuthTokenResponse>
...
}
2. Create an Authenticator class
Implemented okhttp3.Authenticator
interface and override authenticate()
method as shown below:
class TokenAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// This is a synchronous call
val updatedToken = getUpdatedToken()
return response.request().newBuilder()
.header(ApiClient.HEADER_AUTHORIZATION, updatedToken)
.build()
}
private fun getUpdatedToken(): String {
val requestParams = HashMap<String, String>()
...
val authTokenResponse = ApiClient.userApiService.getAuthenticationToken(requestParams).execute().body()!!
val newToken = "${authTokenResponse.tokenType} ${authTokenResponse.accessToken}"
SharedPreferenceUtils.saveString(Constants.PreferenceKeys.USER_ACCESS_TOKEN, newToken)
return newToken
}
}
The authenticate()
method is called when server returns 401 Unauthorized
. For calling ApiClient.userApiService.getAuthenticationToken()
, we're using execute()
to make it a synchronous call.
3. Prepare OkHttp client
Now set this TokenAuthenticator
in OkHttpClient
as shown below:
OkHttpClient.Builder()
.connectTimeout(TIMEOUT_IN_SECONDS.toLong(), TimeUnit.SECONDS)
.readTimeout(TIMEOUT_IN_SECONDS.toLong(), TimeUnit.SECONDS)
.authenticator(TokenAuthenticator())`
.addInterceptor(MyInterceptor())
That is it!!!
Now, whenever you make an API call, i.e. friend requests list API call and get 401, the retrofit will call API to refresh the token and make the same request, i.e. friend requests list call again.
You do not need to handle anything on the view layer for this.
Thanks for reading this article. I hope you liked it and understood the solution easily. For any doubts or suggestions, feel free to comment.
If you're new to Kotlin, you can read my previous blog Features You Will Love About Kotlin which will give you the understanding of this beautiful language.
Top comments (11)
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.
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.
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.
nice article, i think i should use this in my next project π
Thanks Adri. Sure we should use this.
Can I use this solution for parallel request API?
Ofcourse. Make sure you manage
authenticate()
method accordingly.E.g. make
getUpdatedToken()
call synchronizeAwesome, thank you very much!!!!!!!
i need this for my work.
You saved me.
Thanks @ricardo
If refresh token is expired also, and request refresh token return 401, how to handle this case?
Here is the complete code which I used in one project: