0
votes

I understand the flow to use OAuth2 is:

after the short-lived access token expires (server returning 401), the client has to request a new one using the refresh token.

To implement it in an iOS (with AFNetworking) or Android (with Volley) app, I imagine the network manager has to be able to detect returned 401 error and then send request to the auth server.

The problem is with the concurrent usage of the network. Consider the scenario where the access has expired, the app sends 2 requests: req1 and after 100ms, req2. Drawn on a timeline, this looks like:

req1 --> 401 --> (refresh req) --> OK, new access and fresh tokens --> retry req1
  req2 --> 401 --> (refresh req) --> 403, wrong refresh token

The final result is req2 will fail and the app logs user out because of the 403 error.

So my questions are

is this implementation heading towards a right direction? Or it's wrong to refresh after receiving 401? Should I instead refresh the token when the user starts the app (at the cost of slowing down app launch)

How can I solve the concurrency issue?

1
Which library are you using in android for api calling?? - Chandrakant Dvivedi
@ChandrakantDvivedi I am using Volley - Siyu
I am using okHttp, where interceptor is provided in which you can intercept in between request and response. - Chandrakant Dvivedi
Volley can do it too, the problem is then how to exchange the token - Siyu
@Siyu do you already have some token manager implementation that works for sequential requests but may fail on parallel request? - Ján Halaša

1 Answers

1
votes

Since you have an existing token manager, I would add some extra logic into it (in Java):

class TokenManager {

  private String accessToken;
  private CompletableFuture<String> accessTokenRefreshComletableFuture;

  public CompletableFuture<String> getAccessToken() {
    if (this.accessToken is expired) {
       // If refreshed accessToken is being requested
       CompletableFuture<String> runningRequestFuture = this.accessTokenRefreshComletableFuture;
       if (runningRequestFuture == null) {
          // For thread safety, this assignment should be synchronized (or made atomic)
          // with the previous reading
          this.accessTokenRefreshComletableFuture = new CompletableFuture<>();
          // Request a fresh access token.
          // When you get a new access token, set the this.accessTokenRefreshComletableFuture 
          // complete and remove its reference from the manager class.
       }
       return runningRequestFuture;
    }
    // Synchronous result
    return CompletableFuture.completedFuture(this.accessToken);
  }
}

The manager doesn't return an access token, but a CompletableFuture (Promise in JavaScript - asynchronous result). If the access token needs to be refreshed, check first whether the /token endpoint request is already running. If it is, return it's CompletableFuture.

This way, you would always have either a valid access token or a single CompletableFuture waiting for a new access token.