Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Retrofit 2.0 Refresh Tokens

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) {
        }
    });
like image 509
vasile Avatar asked Jun 11 '16 14:06

vasile


3 Answers

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.

like image 104
Yasin Kaçmaz Avatar answered Nov 19 '22 05:11

Yasin Kaçmaz


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()
  
like image 44
Dino Sunny Avatar answered Nov 19 '22 07:11

Dino Sunny


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.

like image 2
Anoop M Maddasseri Avatar answered Nov 19 '22 06:11

Anoop M Maddasseri