I am using Retrofit 2.0
with Jackson
converter for communication with a Rest API. Some of the requests require tokens on authorization. If the tokens that I have are out-of-date, I need to refresh them with another request and repeat the last request that failed because of it.
My question: do I need to do it manually each time or is there any way to automate it ?
Here is the way I implement it at the moment:
TrackerService
public interface TrackerService {
@POST("auth/sendPassword")
Call<ResponseMessage> sendPassword(@Header("app-type") String appType,
@Body User userMobile);
@FormUrlEncoded
@POST("oauth/token")
Call<TokenResponse> oathToken(@Field("client_id") String clientId,
@Field("client_secret") String clientSecret,
@Field("grant_type") String grantType,
@Field("username") String username,
@Field("password") String password);
@FormUrlEncoded
@POST("oauth/token")
Call<TokenResponse> refreshToken(@Field("client_id") String clientId,
@Field("client_secret") String clientSecret,
@Field("grant_type") String grantType,
@Field("refresh_token") String username);
@PUT("me/profile")
Call<Profile> updateProfile(@Header("app-type") String appType,
@Header("Authorization") String token,
@Body Profile profile);
}
ServiceGateway
public class ServiceGateway {
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit retrofit;
public static <S> S createService(Class<S> serviceClass) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
.writeTimeout(20 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(20 * 1000, TimeUnit.MILLISECONDS)
.addInterceptor(interceptor).build();
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(JacksonConverterFactory.create());
retrofit = builder.client(httpClient.build())
.client(client)
.build();
return retrofit.create(serviceClass);
}
public static Retrofit getRetrofit() {
return retrofit;
}
}
How I call function and treat it when tokens are out of date
trackerService = ServiceGateway.createService(TrackerService.class);
Call<Profile> call = trackerService.updateProfile(getString(R.string.app_type), "Bearer " + userPrefs.accessToken().get(),
new Profile(trimedInvitationMessage, title,
String.valueOf(selectedCountry.getCountryCode()), mobilePhone, countryISO, fullName));
call.enqueue(new Callback<Profile>() {
@Override
public void onResponse(Call<Profile> call, Response<Profile> response) {
if (response.body() != null) {
} else {
if (response.raw().code() == 401) {
Call<TokenResponse> refreshTokenCall = trackerService.refreshToken(userPrefs.clientId().get(),
userPrefs.clientSecret().get(), "refresh_token", userPrefs.refreshToken().get());
refreshTokenCall.enqueue(new Callback<TokenResponse>() {
@Override
public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
if (response.body() != null) {
updateAdviserProfile(trimedInvitationMessage, title, mobilePhone, countryISO, fullName);
} else {
userPrefs.clear();
Intent intent = new Intent(WelcomeActivity_.launcher(EditProfileActivity.this));
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
startActivity(WelcomeActivity_.launcher(EditProfileActivity.this));
}
}
@Override
public void onFailure(Call<TokenResponse> call, Throwable t) {
}
});
} else if (response.raw().code() == 422)
}
}
@Override
public void onFailure(Call<Profile> call, Throwable t) {
}
});
I searched this topic since 2-3 months ago and found OkHttp's Authenticator
. You can use it. There is one link here: refreshing-oauth-token-using-retrofit-without-modifying-all-calls
It works like that: If your request returns 401
, then Authenticator
moves in, and refreshes your token. But don't forget to return null
or put any try limit. If you don't limit, it will try to refresh multiple times when your refresh request fails. Also, make synchronous requests when refreshing your token.
Also, I have a question and answer -both written by myself- about refreshing the Oauth2 token:
Question: android-retrofit2-refresh-oauth-2-token
Answer: android-retrofit2-refresh-oauth-2-token-answer
Additionally: For example if you have a token and you need to refresh it per 3 hours. You can write an Interceptor
too. In Interceptor
: compare time and refresh your token without getting any 401
response.
Square's documentation for Interceptor
: OkHttp Interceptors
Square's documentation for Authenticator
: OkHttp handling-authentication
I know there is no code here, but see links and edit your question then I will try to help you.
The authenticate() method is called when the server returns 401 Unauthorized.
For calling ApiFactory.retrofit("url").create(PostDataInterface::class.java) .refreshToken(refreshTokenRequest)), we're using execute() to make it a synchronous call.
if refresh token status is 0. Add your function to logout the user.
interface PostDataInterface {
@POST("refreshUserToken")
fun refreshToken(@Body refreshTokenRequest: RefreshTokenRequest?): Call<RefreshTokenResponse?>?
}
class TokenAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// This is a synchronous call
val updatedToken = getNewToken()
return updatedToken?.let {
response.request.newBuilder().header("Authorization", it)
.build()
}
}
private fun getNewToken(): String? {
val refreshTokenRequest = RefreshTokenRequest(SharedPreferenceHelper.refreshToken)
val call = ApiFactory.retrofit(BuildConfig.BASEURL).create(PostDataInterface::class.java)
.refreshToken(refreshTokenRequest)
val authTokenResponse = call?.execute()?.body()
if (authTokenResponse?.status == 0){
//Logout User
AuthUtility.logout(true)
}
return authTokenResponse?.data?.token
}
}
Add Authenticator in Okhttp client
private val client =
OkHttpClient().newBuilder()
.authenticator(TokenAuthenticator())
...
.build()
Here is the refresh token authenticator implementation
class TokenAuthenticator(
val sharedPrefsHelper: SharedPrefsHelper,
private val identityService: IdentityService
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
Log.d("TokenAuth Request:", "${response.body}")
val refreshToken = sharedPrefsHelper[SharedPrefsHelper.PREF_KEY_AUTH_REFRESH_TOKEN, null]
if (refreshToken.isNullOrEmpty().not()) {
val requestFields = mutableMapOf<String, String>()
requestFields["refresh_token"] = refreshToken!!
requestFields["grant_type"] = "refresh_token"
try {
val tokenResponse = runBlocking {
identityService.getAuthToken(requestFields)
}
Log.d("TokenAuth Success:", "$tokenResponse")
tokenResponse.accessToken.let { accessToken ->
sharedPrefsHelper.put(
SharedPrefsHelper.PREF_KEY_AUTH_TOKEN,
accessToken
)
sharedPrefsHelper.put(
SharedPrefsHelper.PREF_KEY_AUTH_REFRESH_TOKEN,
tokenResponse.refreshToken
)
return response.request.newBuilder()
.header("Authorization", "Bearer $accessToken")
.build()
}
} catch (e: Exception) {
Log.d("TokenAuth Error:", "$e")
}
}
return null
}
}
Configure it with the builder -
return OkHttpClient.Builder()
.authenticator(TokenAuthenticator(sharedPrefsHelper, identityBaseUrl))
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(requestInterceptor).addInterceptor(logging)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
And also as @Yasin suggested -
Don't forget to return null or put any try limit. If you don't limit, it will try to refresh multiple times when your refresh request fails. Also, make synchronous requests when refreshing your token.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With