Using TokenAuthenticator
like @theblang answer is a correct way for handle refresh_token
.
Here is my implement (I have using Kotlin, Dagger, RX but you may use this idea for implement to your case)
TokenAuthenticator
class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {
override fun authenticate(route: Route, response: Response): Request? {
val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
return response.request().newBuilder()
.header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
.build()
}
}
For prevent dependency cycle like @Brais Gabin comment, I create 2 interface like
interface PotoNoneAuthApi { // NONE authentication API
@POST("/login")
fun login(@Body request: LoginRequest): Single<AccessToken>
@POST("refresh_token")
@FormUrlEncoded
fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}
and
interface PotoAuthApi { // Authentication API
@GET("api/images")
fun getImage(): Single<GetImageResponse>
}
AccessTokenWrapper
class
class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
private var accessToken: AccessToken? = null
// get accessToken from cache or from SharePreference
fun getAccessToken(): AccessToken? {
if (accessToken == null) {
accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
}
return accessToken
}
// save accessToken to SharePreference
fun saveAccessToken(accessToken: AccessToken) {
this.accessToken = accessToken
sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
}
}
AccessToken
class
data class AccessToken(
@Expose
var token: String,
@Expose
var refreshToken: String)
My Interceptor
class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val authorisedRequestBuilder = originalRequest.newBuilder()
.addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
.header("Accept", "application/json")
return chain.proceed(authorisedRequestBuilder.build())
}
}
Finally, add Interceptor
and Authenticator
to your OKHttpClient
when create service PotoAuthApi
Demo
https://github.com/PhanVanLinh/AndroidMVPKotlin
Note
Authenticator flow
- Example API
getImage()
return 401 error code
authenticate
method inside TokenAuthenticator
will fired
- Synchronize
noneAuthAPI.refreshToken(...)
called
- After
noneAuthAPI.refreshToken(...)
response -> new token will add to header
getImage()
will AUTO called with new header (HttpLogging
WILL NOT log this call) (intercept
inside AuthInterceptor
WILL NOT CALLED)
If getImage()
still failed with error 401, authenticate
method inside TokenAuthenticator
will fired AGAIN and AGAIN then it will throw error about call method many time(java.net.ProtocolException: Too many follow-up requests
). You can prevent it by count response. Example, if you return null
in authenticate
after 3 times retry, getImage()
will finish and return response 401
If getImage()
response success => we will result the result normally (like you call getImage()
with no error)
Hope it help