PacktPublishing / ASP.NET-Core-2-and-Angular-5

ASP.NET Core 2 and Angular 5, published by Packt
MIT License
79 stars 75 forks source link

Chapter 9 auth.response.interceptor does not work correctly when multiple requests are done nearly at the same time #49

Open JessyParis opened 4 years ago

JessyParis commented 4 years ago

If the cookie has timed out, your interceptor works well if the next API call is a single request. But, for example, if I open a component that needs a lot of API calls, for example to fill 10 dropdown lists, then the app will make 10 API calls nearly at the same time. In this case, the way you store the current request is not good. Indeed, the 10 firsts API request will come back on error, then 10 calls will be made to get a refresh token, and then the reexecuted calls will mostly be the refresh token call and not the original API calls. If you store the current request, not in the authinterceptor itself (currentRequest: HttpRequest), but in the intercept method (let currentRequest: HttpRequest = request.clone();). And then pass the request to the handleError (handleError(err: any, next: HttpHandler, request: HttpRequest)). Then everything works fine and all the original API calls are correctly called again. Regards, PS : thank you for your books that greatly help me!

Code sample (modifications commented //XXXXXXXXXXX)

@Injectable()
export class AuthResponseInterceptor implements HttpInterceptor {
  auth: AuthService;
  constructor(
    private injector: Injector,
    private router: Router,
    private applicationUserContext: ApplicationUserContext
    , public dialog: MatDialog
  ) { }

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler): Observable<HttpEvent<any>> {

    this.auth = this.injector.get(AuthService);
    var token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;

    if (token) {
      // save current request
      let currentRequest: HttpRequest<any> = request.clone(); //XXXXXXXXXXX

      return next.handle(request).pipe(
        tap((event: HttpEvent<any>) => {
          if (event instanceof HttpResponse) {
            // do nothing
          }
        }),
        catchError(error => {
          return this.handleError(error, next, currentRequest) //XXXXXXXXXXX
        }));
    }
    else {
      return next.handle(request);
    }
  }

  handleError(err: any, next: HttpHandler, request: HttpRequest<any>) {  //XXXXXXXXXXX
    if (err instanceof HttpErrorResponse) {
      if (err.status === 403) {
        // JWT token might be expired if the error does not come from a new token request
        if (err.url.endsWith("evapi/token/auth")) {
          //Close all existing dialog
          this.dialog.closeAll();
          //Show error
          showErrorToUser(this.dialog, err, this.applicationUserContext);
          //Route to login
          this.router.navigate(["login"]);
        }
        else {
          // try to get a new one using refresh token
          let previousRequest = request;  //XXXXXXXXXXX
          return this.auth.refreshToken().pipe(
            flatMap((refreshed) => {
              var token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
              if (token) {
                previousRequest = previousRequest.clone({
                  setHeaders: { eValorplastSecurity: token }
                });
              }
              return next.handle(previousRequest);
            }));
        }
      }
      if (err.status === 401) {
        //Forbidden
        this.router.navigate(["forbidden"]);
      }
    }
    return throwError(err);
  }
}