4

I am making use of the Android Async Http Library in my app to make async http requests.

I have come into a situation in my android app where the following happens. My web api makes use of an access token and a refresh token. On every request that I make I check if the access token is still valid. If it is not I then issue a http post to go get a new access token using the refresh token.

Now i have noticed the following situation.

user of my app leaves their phone inactive for enough time for the access token to expire. When they wake the phone up. In my onResume() function I fire off two separate http requests.

  1. Request 1 checks the access token and determines its not valid. it then issues a refreshAccessToken request.
  2. While Request 1 is waiting for the response, Request 2 also checks the access token and determines its not valid. And it also issues a refreshAccessToken request.
  3. Request 1 returns successfully and updates the access token and refresh token values.
  4. Request 2, then gets a 401 response from the api as the refresh token which it provided has already been used. My application then thinks that there is an error with the refreshToken and logs the user out.

This is obviously incorrect and I would like to avoid it. I have in the mean time, done a double check in the refreshAccessToken onFailed() method. To see if the accessToken is maybe valid again. However this is inefficient as I am still sedning two requests over the air, and my API has to handle the failed refresh attempt.

Question: Now my issue is that i cant use any locks or synchronization as you cannot block the main UI thread in android. And Android Async Http Library handles all of the different threads etc.

Zapnologica
  • 22,170
  • 44
  • 158
  • 253
  • 1
    You might need to do some changes here, if you send requests 1 and 2 in parallel, then you can't log the user out if you get a 401. If you want to keep it that way, then fire request 2 when request 1 is terminated. If you want to send both in the same time to be a few millisec faster, then don't log the user out if request 2 results in a 401, but only if request 1 does. – Mostrapotski May 15 '15 at 11:34

2 Answers2

2

Request 2 also checks the access token and determines its not valid.

This is wrong. Since Request 1 may have already issued refreshAccessToken request, then the state of the access token cannot be determined by consulting the server.

So you need a combined operation getAccessToken() which checks access token, issues refreshAccessToken when needed, and, when called in parallel, only waits for previously called getAccessToken() operation.

UPDATE. refreshAccessToken is a part of a class which serves as a gatekeeper and allows requests to run only if access token is refreshed. If token is not refreshed, gatekeeper sends single request to refresh the token. Meantime, input requests are saved in a queue. When the token is refreshed, the gatekeeper lets saved requests to run.

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
  • Yes exactly, Except how exactly do I achieve that? I have a function which gets the refresh and access token, But how but do I prevent parallel requests without blocking the main thread? – Zapnologica May 17 '15 at 18:09
  • you told Request 1 and Request 2 run in parallel. I assume they use own threads (if not, you already are blocking the main thread). – Alexei Kaigorodov May 17 '15 at 18:32
  • Making use of async http lib, So main thread builds and then kicks off both requests. But they are executed on a different thread managed by the library. My main thread just builds up the request. So I would have to block the execution of request 2 till request 1 has returned. Ie , I would have to block it in the main thread so that it doesn't tell the library to execute it. – Zapnologica May 17 '15 at 18:47
  • If you use an async library, then what does "Request 1 is waiting for the response" mean? – Alexei Kaigorodov May 18 '15 at 17:36
1

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;
            }
        });
  • If you're going to provide the same exact answer multiple times, please explain what your code is supposed to do – emsimpson92 Sep 07 '18 at 22:07