4
votes

I am new to android programming and Retrofit , I am making a sample app where i have to make two parallel network calls using access token. The problem comes when access token is expired and return 401 status code , if I see 401 HTTP status code I have to make a call to refresh token with this access token , but problem with parallel calls is that it leads to race condition for refreshing the refresh token , is there any best practice of way to avoid such situation and how to intelligently refresh the token without any conflict.

3
did you find the answer? I am also facing the same problem.Mohit Rajput

3 Answers

0
votes

OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.

public class TokenAuthenticator implements Authenticator {
    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {
        // Refresh your access_token using a synchronous api request
        newAccessToken = service.refreshToken();

        // Add new header to rejected request and retry it
        return response.request().newBuilder()
            .header(AUTHORIZATION, newAccessToken)
            .build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        // Null indicates no attempt to authenticate.
        return null;
    }

Attach an Authenticator to an OkHttpClient the same way you do with Interceptors

OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);

Use this client when creating your Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint(ENDPOINT)
            .setClient(new OkClient(okHttpClient))
            .build();
return restAdapter.create(API.class);

Check this: Fore more details visit this link

0
votes

Try to make a queue for the refresh token operations like:

class TokenProcessor {
    private List<Listener> queue = new List<Listener>();
    private final Object synch = new Object();
    private State state = State.None;
    private String token;
    private long tokenExpirationDate;

    public void getNewToken(Listener listener){
        synchronized(synch) {

            // check token expiration date
            if (isTokenValid()){
                listener.onSuccess(token);
                return;
            }
            queue.add(listener);

            if (state != State.Working) {
                sendRefreshTokenRequest();
            }
        }
    }
    private void sendRefreshTokenRequest(){
        // get token from your API using Retrofit
        // on the response call onRefreshTokenLoaded() method with the token and expiration date
    }
    private void onRefreshTokenLoaded(String token, long expirationDate){
        synchronized(synch){
            this.token = token;
            this.tokenExpirationDate = expirationDate;

            for(Listener listener : queue){
                 try {
                   listener.onTokenRefreshed(token);
                 } catch (Throwable){}
            }
            queue.clear();                
        }
    }
}

This is an example code, how it can be implemented.

0
votes

To avoid race conditions, you could synchronize refresh token code using ReentrantLock. For instance, if request A and request B try to refresh token at the same time, since code is synchronized, refresh A gets to actually refresh the token. Once it completes, request B will run refreshToken() and there's should be some logic that tells request B that token has already been refreshed. An example could be storing timestamp of when the token refresh happens then check if token has been refreshed last 10 seconds.

val lock = ReentrantLock(true)

fun refreshToken(): Boolean {
     lock.lock()
     if (token has been refreshed in last 10 seconds): return true
     api.refresh()
     lock.unlock()

}

If you don't want to use last 10 seconds logic, here's a different approach. Whenever you refresh token, backend returns {accessToken, expiration-timestamp}. Now, request A saves this token and expiration in disk. Request B will just need to check to make sure token is not expired using the timestamp. If request B gets 401 and token has not expired, it means request A has refreshed the token. Sample code:

val lock = ReentrantLock(true)

fun refreshToken(): Boolean {
     lock.lock()
     if (token has not expired): return true
     api.refresh()
     lock.unlock()

}

Otherwise, you probably have to create a queue for refresh token operations as mentioned above.