0
votes

I am trying to handle API requests using Oauth2 authentication.

I have an authService with three attributes (accessToken, refreshToken & refreshTokenObservable – to be described later) and the apiGet() method which creates HTTP GET request with the Authorization header, like so:

apiGet(cleanedPath:string) {
  return this.config.getConfig().pipe(mergeMap((config: Config) => {
    
    [...] // Cleans parameters and creates a final query string

    // Adding access token as header
    let headers: object = {headers: new HttpHeaders().set('Authorization', this.accessToken)}

    return this.http.get<any>(cleanedPath, headers).pipe(catchError(this.handleApiGetError.bind(this, cleanedPath)));
  }));
}

I want the handleApiGetError() method to call for a new refresh token and try to repeat the apiGet(), where I tried to connect multiple apiGet() calls to the single observable, like so:

private handleApiGetError(cleanedPath:string, error: HttpErrorResponse) {
  if (!this.refreshTokenObservable) {
    // getAccessByRefreshToken() is simple HTTP post
    this.refreshTokenObservable = this.getAccessByRefreshToken();
  }
  return this.refreshTokenObservable.pipe(mergeMap(()=> {
    return this.apiGet(cleanedPath);
  }));
}

This, however, did not work as it called for the new token with every failed apiGet(), causing OAuth service to return multiple E401s (token was already revoked).

Is there any way to bind the apiGet() Observers together and in the case of error call the refresh token request only once with re-calling the failed apiGet() calls?

1

1 Answers

0
votes

Consider using retryWhen to retry only once, and most importantly to inverse the control, so that your ErrorHandler is not responsible for retrying again (by calling its caller).
Using retryWhen your HTTP request will only be retried once the Observable returned by this.handleApiGetError() has been completed, as demonstrated below:

apiGet(cleanedPath: string) {
  return this.config.getConfig().pipe(
    mergeMap((config: Config) => {
      return this.http.get<any>(cleanedPath, headers).pipe(
        retryWhen(x => this.handleApiGetError())
      );
    })
  );
}

private handleApiGetError(error: HttpErrorResponse) {
  return this.refreshTokenObservable;
}

That being said, handleApiGetError might be unnecessary as you can instead call getAccessByRefreshToken directly, as following:

 apiGet(cleanedPath: string) {
   return this.config.getConfig().pipe(
     mergeMap((config: Config) => {
       return this.http.get<any>(cleanedPath, headers).pipe(
         retryWhen(this.getAccessByRefreshToken)
       );
     })
   );
 }