I'm encountering an issue in my Angular application where parallel HTTP requests are causing multiple refresh token requests upon receiving a 401 response from the server. Here's the scenario:

My Angular application sends multiple HTTP requests in parallel. If any of these requests returns a 401 (Unauthorized) response due to an expired access token, the application initiates a refresh token request to obtain a new access token. However, if multiple requests receive the 401 response simultaneously, the application sends multiple refresh token requests, which is unnecessary and could lead to performance issues and potential race conditions.

I'm looking for guidance on how to optimize my Angular application to handle this situation more efficiently. Specifically, I need to ensure that only one refresh token request is sent, even if multiple requests receive a 401 response concurrently.

Here is how I am initiating requests from component:

let URL = this.baseURL + "api/features?limit=" + this.perPage + '&page=1';
    let headers = new HttpHeaders({
      "Accept": "application/json",
      "Access-Control-Allow-Origin": '*',
      "Content-Type": "application/json",
    });
    let body = {};

    this.httpClientRequest.initiateHttpRequest(URL, body, headers, "GET").subscribe(
      (response) => {
        this.featureFilteration(response, filteredModules, false)
      },
      (error) => {
        //error() callback


        // this.toastMessages.decryptAndDisplayErrorMessage(error,'getFeatures');

        if (error.status == 401 && error.statusText == "Unauthorized" && error.error.message == "Unauthenticated.") {
          console.log('entered in refesh')
          this.refreshToken.refreshExpiredToken(URL, body, headers, "GET").subscribe(
            (response) => {
              this.featureFilteration(response, filteredModules, true)
            },
            (error) => {
              // Handle errors here
              console.error('Request failed with error', true);
              this.showSpinner = false;
            }
          );
        }

      });
` this.httpClientRequest.initiateHttpRequest(URL,body,headers,method).subscribe(
    (response) => {
      this.showSpinner = false;
      this.toastMessages.showToast('','Add-Ons successfully created','success');
      this.createAddOns.reset();
      this.deleteBtn.nativeElement.click();
    },
    (error) => {
      if(error.error.app_data !== undefined)
      {
        let decrypted_data = JSON.parse(this.encryptDecrypt.decrypt( error.error.app_data));
        this.toastMessages.showToast('',decrypted_data.message,'error');
      }

      this.showSpinner = false;
      if(error.status == 401 && error.statusText == "Unauthorized" && error.error.message ==      "Unauthenticated.")
      {
        this.showSpinner = false;
        this.refreshToken.refreshExpiredToken(URL,body,headers,method).subscribe(
          (data) => {
            // Handle the data received from the refreshExpiredToken method
          },
          (error) => {
            // Handle errors here
            console.error('Request failed with error', error);
            let decrypted_data = JSON.parse(this.encryptDecrypt.decrypt(error.error.app_data));
            this.toastMessages.showToast('',decrypted_data.message,'error');
            this.showSpinner = false;
          }
        );
      }

    });`

My HttpClientRequest Service:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from "@angular/common/http";
import {Observable, switchMap, throwError} from "rxjs";
import { EncryptDecryptService } from "./encrypt-decrypt.service";
import {catchError} from "rxjs/operators";
import {RefreshTokenService} from "./refresh-token.service";
@Injectable({
  providedIn: 'root'
})
export class HttpClientRequestService {

  // @ts-ignore
  constructor(private http: HttpClient, private _EncryptDecryptService: EncryptDecryptService) { }



  initiateHttpRequest(url: string = "", body: any = "", headers: HttpHeaders = new HttpHeaders() , method:string = 'POST' ): Observable<any> {

    if(!url.includes("api/login"))
    {

      const authToken = JSON.parse(this._EncryptDecryptService.decrypt(localStorage.getItem('isUserToken')));
      headers = headers.set("Authorization", "Bearer " + authToken);
    }

    let options = { headers: headers };
    switch (method) {
      case 'GET':
        return this.http.get(url, options);
        break;
      case 'DELETE':
        return this.http.delete(url, options);
        break;
      case 'PUT':
        return this.http.put<any>(url, this.encryptAES256CBC(body), options);
        break;
      default:
        return this.http.post(url, this.encryptAES256CBC(body), options);
        break;
    }
  }

  encryptAES256CBC(dataForEncryption: any) {
    console.log('before encryption', dataForEncryption)
    let encrypted_body = this._EncryptDecryptService.encrypt(dataForEncryption);
    let newDataForEncryption = {
      "app_data": encrypted_body
    }
    console.log('before', newDataForEncryption)
    return newDataForEncryption;
  }

  decryptAES256CBC(dataForDecryption: object) {
    return this._EncryptDecryptService.decrypt(JSON.stringify(dataForDecryption))
  }

}

My Refresh token service which refresh expired token and then initiate request which received 401:

import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {HttpClientRequestService} from "./http-client-request.service";
import {EncryptDecryptService} from "./encrypt-decrypt.service";
import {environment} from "../../environments/environments";
import {AuthService} from "./auth.service";
import {AppComponent} from "../app.component";
import {catchError, concatMap, from, Observable, of, Subject, switchMap, tap, throwError, toArray} from "rxjs";

@Injectable({
  providedIn: 'root'
})
export class RefreshTokenService {
  baseURL: string = environment.apiBASEURL;
  private queue: any[] = [];
  private refreshingToken: boolean = false;
  constructor(private httpClientRequest: HttpClientRequestService, private authService: AuthService,  private encryptDecrypt: EncryptDecryptService) { }



  refreshExpiredToken(
    recursiveURL: string = '',
    recursiveBody: any = '',
    recursiveHeaders: HttpHeaders = new HttpHeaders(),
    recursiveMethod: string = ''
  ): Observable<any> {
    return new Observable<any>((observer) => {
      const localUserJson = localStorage.getItem('isUserToken');
      if (localUserJson) {
        const authToken = JSON.parse(this.encryptDecrypt.decrypt(localUserJson));
        let URL = this.baseURL + 'api/refresh?device=web';
        let headers = new HttpHeaders({
          Accept: 'application/json',
          'Access-Control-Allow-Origin': '*',
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + authToken,
        });
console.log('refresh initiate start')
        // First, refresh the token
        this.httpClientRequest.initiateHttpRequest(URL, {}, headers, 'GET').pipe(
          catchError((error) => {
            console.error('Token refresh failed', error);
            return throwError(error);
          }),
          switchMap((response) => {
            const decrypted_data = JSON.parse(this.encryptDecrypt.decrypt(response.app_data));
            console.log('Token refresh response', decrypted_data);
            if (decrypted_data.success === true) {
              let token = decrypted_data.data.token;
              // Save the refreshed token in localStorage & state
              localStorage.setItem('isUserToken', this.encryptDecrypt.encrypt(token));
              this.authService.SetState(token);

              if (recursiveURL !== '') {
                console.log('Entering the conditional block for recursive call');
                // Send the recursive call and return its response
                return this.httpClientRequest.initiateHttpRequest(recursiveURL, recursiveBody, recursiveHeaders, recursiveMethod).pipe(
                  catchError((error) => {
                    console.error('Recursive call failed', error);
                    return throwError(error);
                  })
                );
              } else {
                // If there's no recursive URL, return an empty observable
                return of(null);
              }
            } else {
              // Handle token refresh failure
              observer.error('Token refresh failed');
              return throwError('Token refresh failed');
            }
          })
        ).subscribe(
          (response) => {
            if (response !== null && response !== undefined && response.app_data !== undefined) {
              console.log('Recursive call response', JSON.parse(this.encryptDecrypt.decrypt(response.app_data)));
              observer.next(JSON.parse(this.encryptDecrypt.decrypt(response.app_data)));
            }
            observer.complete();
          },
          (error) => {
            // Handle errors during the recursive call
            console.error('Recursive call failed', error);
            observer.error(error);
          }
        );
      } else {
        // Handle the case where there is no user token
        this.authService.Logout();
        observer.complete();
      }
    });
  }
}

I tried added Http Interceptor for queuing parallel requests but not succeeded.

0

There are 0 best solutions below