Okhttp refresh expired token when multiple requests are sent to the server

Viewed 14024

I have a ViewPager and three webservice calls are made when ViewPager is loaded simultaneously.

When first one returns 401, Authenticator is called and I refresh the token inside Authenticator, but remaining 2 requests are already sent to the server with old refresh token and fails with 498 which is captured in Interceptor and app is logged out.

This is not the ideal behaviour I would expect. I would like to keep the 2nd and 3rd request in the queue and when the token is refreshed, retry the queued request.

Currently, I have a variable to indicate if token refresh is ongoing in Authenticator, in that case, I cancel all subsequent request in the Interceptor and user has to manually refresh the page or I can logout the user and force user to login.

What is a good solution or architecture for the above problem using okhttp 3.x for Android?

EDIT: The problem I want to solve is in general and I would not like to sequence my calls. i.e. wait for one call to finish and refresh the token and then only send rest of the request on the activity and fragment level.

Code was requested. This is a standard code for Authenticator:

public class CustomAuthenticator implements Authenticator {

    @Inject AccountManager accountManager;
    @Inject @AccountType String accountType;
    @Inject @AuthTokenType String authTokenType;

    @Inject
    public ApiAuthenticator(@ForApplication Context context) {
    }

    @Override
    public Request authenticate(Route route, Response response) throws IOException {

        // Invaidate authToken
        String accessToken = accountManager.peekAuthToken(account, authTokenType);
        if (accessToken != null) {
            accountManager.invalidateAuthToken(accountType, accessToken);
        }
        try {
                // Get new refresh token. This invokes custom AccountAuthenticator which makes a call to get new refresh token.
                accessToken = accountManager.blockingGetAuthToken(account, authTokenType, false);
                if (accessToken != null) {
                    Request.Builder requestBuilder = response.request().newBuilder();

                    // Add headers with new refreshToken

                    return requestBuilder.build();
            } catch (Throwable t) {
                Timber.e(t, t.getLocalizedMessage());
            }
        }
        return null;
    }
}

Some questions similar to this: OkHttp and Retrofit, refresh token with concurrent requests

4 Answers

I found the solution with authenticator, the id is the number of the request, only for identification. Comments are in Spanish

 private final static Lock locks = new ReentrantLock();

httpClient.authenticator(new Authenticator() {
            @Override
            public Request authenticate(@NonNull Route route,@NonNull Response response) throws IOException {

                Log.e("Error" , "Se encontro un 401 no autorizado y soy el numero : " + id);

                //Obteniendo token de DB
                SharedPreferences prefs = mContext.getSharedPreferences(
                        BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

                String token_db = prefs.getString("refresh_token","");

                //Comparando tokens
                if(mToken.getRefreshToken().equals(token_db)){

                    locks.lock(); 

                    try{
                        //Obteniendo token de DB
                         prefs = mContext.getSharedPreferences(
                                BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

                        String token_db2 = prefs.getString("refresh_token","");
                        //Comparando tokens
                        if(mToken.getRefreshToken().equals(token_db2)){

                            //Refresh token
                            APIClient tokenClient = createService(APIClient.class);
                            Call<AccessToken> call = tokenClient.getRefreshAccessToken(API_OAUTH_CLIENTID,API_OAUTH_CLIENTSECRET, "refresh_token", mToken.getRefreshToken());
                            retrofit2.Response<AccessToken> res = call.execute();
                            AccessToken newToken = res.body();
                            // do we have an access token to refresh?
                            if(newToken!=null && res.isSuccessful()){
                                String refreshToken = newToken.getRefreshToken();

                                    Log.e("Entra", "Token actualizado y soy el numero :  " + id + " : " + refreshToken);

                                    prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
                                    prefs.edit().putBoolean("log_in", true).apply();
                                    prefs.edit().putString("access_token", newToken.getAccessToken()).apply();
                                    prefs.edit().putString("refresh_token", refreshToken).apply();
                                    prefs.edit().putString("token_type", newToken.getTokenType()).apply();

                                    locks.unlock();

                                    return response.request().newBuilder()
                                            .header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
                                            .build();

                             }else{
                                //Dirigir a login
                                Log.e("redirigir", "DIRIGIENDO LOGOUT");

                                locks.unlock();
                                return null;
                            }

                        }else{
                            //Ya se actualizo tokens

                            Log.e("Entra", "El token se actualizo anteriormente, y soy el no : " + id );

                            prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

                            String type = prefs.getString("token_type","");
                            String access = prefs.getString("access_token","");

                            locks.unlock();

                            return response.request().newBuilder()
                                    .header("Authorization", type + " " + access)
                                    .build();
                        }

                    }catch (Exception e){
                        locks.unlock();
                        e.printStackTrace();
                        return null;
                    }


                }
                return null;
            }
        });
Related