Skip to main content

Automatic Refresh API Access Token with Retrofit and Okhttp Authenticator

This tutorial will be tell how to use Okhttp Authenticator with Retrofit. This authenticator method is usefull when we having a case like :
  • Re-authorization without a user interaction (login)
  • Requesting new API access token with refresh token
This tutorial will cover the second case (access token dan refresh token) which is commonly use. The case will have scenario like this :
  1. Normal API request.
  2. When token or API key expired, API will return 401.
  3. Okhttp Authenticator will intercept the process.
  4. Okhttp Authenticator will requesting new token with refresh token.
  5. New token receive and API request will be reloaded automatically.
So, first create ApiAdapter class which is a class that contain retrofit singleton. In this class add method getAdapter().

public class ApiClient {
    public static Retrofit getAdapter() {
        if (retrofit==null) {
           final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
           httpClient.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Request request = chain.request()
                              .newBuilder()
                              // add Authorization key on request header
                              // key will be using access token
                              .addHeader("Authorization", preferences.getAccessToken()) 
                              .build();
                   return chain.proceed(request);
               }
           });
           retrofit= new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(httpClient.build())
                .build();
       }
       return retrofit;
    }
}
Notice in this code we use Okhttp Interceptor which is use to add header request as for Authorization Key (Access Token). With this code we can access API, but when the token expire will resulting in error 401 to client. To prevent that, we need to request a new access token.

To handle this case let's use Okhttp Authenticator. The Authenticator will automatically request new token when API return 401 error. Now, we add this code on the getAdapter() method.
httpClient.authenticator(new Authenticator() {
    @Nullable
    @Override
    public Request authenticate(Route route, Response response) throws IOException {
      // check if "the failed request Authorization key" is different from new authorization key
      // to prevent looping the request for new key
      if (!response.request().header("Authorization").equals(preferences.getAccessToken()))
          return null; // stop the authenticator from trying to renew the authorization key
     
      // request new key
    }
 });
The code for getAdapter() method will look like this.
public static Retrofit getAdapter() {
    if (retrofit==null) {
       final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
       httpClient.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request()
                          .newBuilder()
                          .addHeader("Authorization", preferences.getAccessToken()) 
                          .build();
               return chain.proceed(request);
           }
       });
  
       httpClient.authenticator(new Authenticator() {
           @Nullable
           @Override
           public Request authenticate(Route route, Response response) throws IOException {
             if (!response.request().header("Authorization").equals(preferences.getAccessToken()))
                 return null;
            
             // request new key
           }
       });
  
       retrofit= new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient.build())
            .build();
   }
   return retrofit;
}
For the last part we add the code to request new access token. Based on the case, we will use retrofit to request new key. Now we create a new static retrofit method called getAdapterRefresh(), this method will look like getAdapter() method but without authenticator and using the different Authorization Key (refresh token).
public static Retrofit getAdapterRefresh() {
    if (retrofitRefresh==null) {
       final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
       httpClient.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request()
                          .newBuilder()
                          // add Authorization key on request header
                          // key will be using refresh token
                          .addHeader("Authorization", preferences.getRefreshToken()) 
                          .build();
               return chain.proceed(request);
           }
       });
       retrofitRefresh= new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient.build())
            .build();
   }
   return retrofitRefresh;
}
Now let's add code to request new token on authenticator.
httpClient.authenticator(new Authenticator() {
    @Nullable
    @Override
    public Request authenticate(Route route, Response response) throws IOException {
        if (!response.request().header("Authorization").equals(sessPref.getAccessToken()))
          return null;
 
        String accessToken = null;
        ApiInterface apiService = ApiClient.getAdapterRefresh(context).create(ApiInterface.class);
        Call<TokenResponse> call = apiService.requestAccessToken();
          try {
            retrofit2.Response responseCall = call.execute();
            TokenResponse responseRequest = (TokenResponse) responseCall.body();
            if (responseRequest != null) {
              TokenDataResponse data = responseRequest.getData();
              accessToken = data.getAccessToken();
              // save new access token
              preferences.setAccessToken(accessToken);
            }
          }catch (Exception ex){
            Log.d("ERROR", "onResponse: "+ ex.toString());
          }
 
          if (accessToken != null)
            // retry the failed 401 request with new access token
            return response.request().newBuilder()
                  .header("Authorization", accessToken) // use the new access token
                  .build();
          else
            return null;
    }
});
Then the full getAdapter() code will look like this.
public static Retrofit getAdapter() {
    if (retrofit==null) {
       final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
       httpClient.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request()
                          .newBuilder()
                          .addHeader("Authorization", preferences.getAccessToken()) 
                          .build();
               return chain.proceed(request);
           }
       });
  
       httpClient.authenticator(new Authenticator() {
           @Nullable
           @Override
           public Request authenticate(Route route, Response response) throws IOException {
               if (!response.request().header("Authorization").equals(preferences.getAccessToken()))
                   return null;
              
               // request new key
               String accessToken = null;
               ApiInterface apiService = ApiClient.getAdapterRefresh(context).create(ApiInterface.class);
               Call<TokenResponse> call = apiService.requestAccessToken();
               try {
                   retrofit2.Response responseCall = call.execute();
                   TokenResponse responseRequest = (TokenResponse) responseCall.body();
                   if (responseRequest != null) {
                     TokenDataResponse data = responseRequest.getData();
                     accessToken = data.getAccessToken();
                     // save new access token
                     preferences.setAccessToken(accessToken);
                   }
               }catch (Exception ex){
                 Log.d("ERROR", "onResponse: "+ ex.toString());
               }
 
               if (accessToken != null)
                 // retry the failed 401 request with new access token
                 return response.request().newBuilder()
                       .header("Authorization", accessToken) // use the new access token
                       .build();
               else
                 return null;
           }
        });
  
       retrofit= new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient.build())
            .build();
   }
   return retrofit;
}

Final code will be like this.
public class ApiClient {
  private static Retrofit retrofitRefresh = null;
  private static Retrofit retrofit = null;
  public static Retrofit getAdapterRefresh() {
      if (retrofitRefresh==null) {
         final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
         httpClient.addInterceptor(new Interceptor() {
              @Override
              public Response intercept(Chain chain) throws IOException {
                  Request request = chain.request()
                            .newBuilder()
                            // add Authorization key on request header
                            // key will be using refresh token
                            .addHeader("Authorization", preferences.getRefreshToken()) 
                            .build();
                 return chain.proceed(request);
             }
         });
         retrofitRefresh= new Retrofit.Builder()
              .baseUrl(BASE_URL)
              .addConverterFactory(GsonConverterFactory.create())
              .client(httpClient.build())
              .build();
     }
     return retrofitRefresh;
  }

  public static Retrofit getAdapter() {
      if (retrofit==null) {
         final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
         httpClient.addInterceptor(new Interceptor() {
              @Override
              public Response intercept(Chain chain) throws IOException {
                  Request request = chain.request()
                            .newBuilder()
                            .addHeader("Authorization", preferences.getAccessToken()) 
                            .build();
                 return chain.proceed(request);
             }
         });
  
         httpClient.authenticator(new Authenticator() {
             @Nullable
             @Override
             public Request authenticate(Route route, Response response) throws IOException {
                 if (!response.request().header("Authorization").equals(preferences.getAccessToken()))
                     return null;
                
                 // request new key
                 String accessToken = null;
                 ApiInterface apiService = ApiClient.getAdapterRefresh(context).create(ApiInterface.class);
                 Call<TokenResponse> call = apiService.requestAccessToken();
                 try {
                     retrofit2.Response responseCall = call.execute();
                     TokenResponse responseRequest = (TokenResponse) responseCall.body();
                     if (responseRequest != null) {
                       TokenDataResponse data = responseRequest.getData();
                       accessToken = data.getAccessToken();
                       // save new access token
                       preferences.setAccessToken(accessToken);
                     }
                 }catch (Exception ex){
                   Log.d("ERROR", "onResponse: "+ ex.toString());
                 }
 
                 if (accessToken != null)
                   // retry the failed 401 request with new access token
                   return response.request().newBuilder()
                         .header("Authorization", accessToken) // use the new access token
                         .build();
                 else
                   return null;
             }
          });
  
         retrofit= new Retrofit.Builder()
              .baseUrl(BASE_URL)
              .addConverterFactory(GsonConverterFactory.create())
              .client(httpClient.build())
              .build();
     }
     return retrofit;
  }
}

Comments

Post a Comment

Popular posts from this blog

How to add host ip address on Android Virtual Device (AVD) or Genymotion using Android Studio

Select  terminal Enter this script AVD adb shell echo '10.0.2.2 hostname' /system/etc/hosts Genymotion adb shell echo '10.0.3.2 hostname' /system/etc/hosts Enter this script to check if the host successfully added adb shell cat /system/etc/hosts Then terminal will print the hosts file list  Enter http://hostname from emulator browser to check if the host can be accessed