Angular HTTP Interceptor delays processing of http requests until a new refresh token is obtained

After creating my AuthInterceptor to handle 401 errors by requesting a new token, I encountered a problem. The handle401Error method is supposed to wait for other HTTP requests until the new token is received, but it isn't waiting as expected. Even though it successfully retrieves a new access token through an HTTP call, it doesn't pause until the refresh token is obtained. Please refer to the attached screenshot for more details.

https://i.sstatic.net/CdJlN.png

Interceptor.ts

isRefreshingToken = false;
tokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null);

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

    const timeOut = appSettings.ajaxTimeout;
    const retryCount = appSettings.retryCount;
    const retryDelay = appSettings.retryDelayMS;

    return next.handle(request).pipe(
        timeout(timeOut),
        catchError((error) => {
            if (error instanceof HttpErrorResponse) {
                const httpErrorCode: number = error['status'];
                switch (httpErrorCode) {
                    case StatusCodes.BAD_REQUEST:
                        return throwError(error);
                    case StatusCodes.UNAUTHORIZED:
                        return this.handle401Error(request, next);
                    default:
                        this._toastr.error(
                            'Sorry! something went wrong.',
                            'Error!'
                        );
                        return throwError(error);
                }
            } else {
                return throwError(error);
            }
            }),
            retryWhen((errors) => {
                return errors.pipe(
                    concatMap((error, count) => {
                        if (count < retryCount) {
                            return of(error);
                        }
                        return throwError(error);
                    }),
                    delay(retryDelay)
                );
            })
        );
    }

    // Rest of the code follows...

Answer №1

The problem has been successfully resolved by implementing the code provided below. If there are any alternative solutions or improvements, please feel free to share.

private isRefreshingToken = false;
private timeOut = appSettings.ajaxTimeout;
private tokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

intercept(
    request: HttpRequest<any>,
    next: HttpHandler
): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
    timeout(this.timeOut),
    catchError((error) => {
        if (error instanceof HttpErrorResponse) {
        const httpErrorCode: number = error['status'];
        switch (httpErrorCode) {
            case StatusCodes.BAD_REQUEST:
              return throwError(error);
            case StatusCodes.UNAUTHORIZED:
              return this.handle401Error(request, next);
            default:
              this._toastr.error(
                'Sorry! something went wrong.',
                'Error!'
              );
              return throwError(error);
           }
           } else {
             return throwError(error);
           }
       })
    );
}

private handle401Error(
    request: HttpRequest<any>,
    next: HttpHandler
): Observable<HttpEvent<any>> {
        if (!this.isRefreshingToken) {
            this.isRefreshingToken = true;
            // Reset here so that the following requests wait until the token
            // comes back from the refreshToken call.
            this.tokenSubject.next(null);
            return this._spotify.getRefreshToken().pipe(
                switchMap((response: ISpotifyTokens) => {
                    console.log('updating on 401');
                    // Updating new token in cookie
                    this._spotify.updateTokensInStorage(response, false);
                    this.tokenSubject.next(response.access_token);
                    return next.handle(
                        this.addTokenInHeader(request, response.access_token)
                    );
                }),
                catchError((error) => {
                    // If there is an exception calling 'refreshToken', bad news so logout.
                    this.logoutUser();
                    return throwError('');
                }),
                finalize(() => {
                    this.isRefreshingToken = false;
                })
            );
        } else {
            return this.tokenSubject.pipe(
                filter((token) => token != null),
                take(1),
                switchMap((token) => {
                    return next.handle(this.addTokenInHeader(request, token));
                })
            );
        }
    }

addTokenInHeader(
    request: HttpRequest<any>,
    token: string
): HttpRequest<any> {
    return request.clone({
        setHeaders: { Authorization: 'Bearer ' + token }
    });
}

logoutUser(): void {
    this._spotify.clearTokensFromStorage();
    this._router.navigate(['/welcome']);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating a Utils class in Vue.js with seamless access to Vuex through this.$store

I have a situation where I need to retrieve state from the Vuex store using this.$store. After some research, I discovered that creating a custom plugin with an installed instance method might be the solution. Here is my plugin implementation: index.ts i ...

Reducing SCSS import path in Angular 7

Creating a component that is deeply nested raises the issue of importing shared .scss files with long paths: @import '../../../app.shared.scss'; This hassle doesn't exist when it comes to .ts files, thanks to the configuration in tsconfig. ...

Angular Resolvers - Achieving Array Success Without Observables. What's the secret behind this accomplishment?

When implementing resolvers in Angular routing, the official documentation suggests using objects in the routing configuration and utilizing an observer in the component to access the resolved data from the activated route. However, I came across a differ ...

html td elements are breaking due to the application of ngFor

<table> <tr> <th>Date</th> <th>Products</th> </tr> <tr *ngFor="let x of orderdetails"> <td>{{x.orderdate}}</td> <td *ngFor="let y of x.orderproducts"> <span class="break">{{y}}</s ...

Synchronized scrolling and equal height for multiple divs

Looking to create a system similar to GitHub's conflict resolver for my project. I have multiple versions represented by values in columns, and I want to be able to select different values from each column to create a new solution. It's important ...

Checking the conditions and return statements within Jest testing framework

I recently started diving into Jest and am looking for alternative methods to test this function regarding the If case and return statement using Jest. Here is the function I need help testing: const extractInfo = (description: string) => { cons ...

Tips for utilizing ng-template in various components

Is there a way to display an <ng-template> in different components? For example, let's take the component test with the following structure: test.html <ng-template #content > <p> Hello world </p> </ng-template> test. ...

JavaScript: Trouble accessing .target property for click event in EventTarget object

After converting my project from regular JavaScript to TypeScript, I encountered an issue where the target property of a base class EventTarget was not being recognized by TypeScript. This property worked perfectly fine in JS, so it must exist. Could it be ...

Utilize style as a module in Angular

The issue at hand: Let's take a look at this project structure: /src /public /styles /general /tables.scss /secure /components /someTable1 /someTable.component.ts /someTable.component.css /someTa ...

Deactivating Google Map Clustering for Individual Markers

I'm currently utilizing Angular 4, Google Maps v3, and Marker Clusterer v2 - all of which are the latest versions. I'm attempting to implement a straightforward example found in the official Google Maps documentation (https://developers.google.co ...

Angular2 is throwing a TypeError, stating that OrbitControls is not a valid constructor

Currently, I am delving into the world of angular2 and three.js. Here is how my angular cli configuration looks like. Inside package.json: "three": "^0.81.2", "three-stl-loader": "^1.0.4", In the angular.cli.json: "../node_modules/three/build/thr ...

Tips for showcasing unique keywords in Ace Editor within the Angular framework

Can anyone help me with highlighting specific keywords in Angular using ace-builds? I've tried but can't seem to get it right. Here's the code snippet from my component: Check out the code on Stackblitz import { AfterViewInit, Component, ...

Tips for utilizing the "??=" syntax in Typescript

let x; x ??= 'abc' console.log(x); // abc Running the code above in the browser console does not cause any issues. However, when attempting to run it in TypeScript, an error is thrown. SyntaxError: Unexpected token '??=' Here is my c ...

Learn the steps for implementing i18n-x in Angular 2 to localize constant property values

I am currently working on localizing my Angular2 application using the i18n-x form from here. It has been successful for attributes like so: <p-dialog i18n-header header="User Details"></p-dialog> The result is: <trans-unit id="fe871da89f ...

Having trouble integrating a Bootstrap-based ecommerce theme into an Angular8 project?

I have a bootstrap-based ecommerce theme with all the necessary files. The HTML theme is loading perfectly. Check out the official theme page I am integrating into my angular8 project: Theme Page Link I have created a repository for my angular8 project ...

What could have caused the sudden halt of fetching on all server branches in the backend?

Following a code refactor on a separate branch, the fetch function ceases to work in any branch despite everything else functioning correctly. The error message reads: ...server/KE/utils.ts:44 const response = await fetch( ^ ReferenceError ...

Triggering a class in Angular when another class is activated through JavaScript

My goal is to apply the class "xyz" when the class "xy" is activated using ngClass. I am looking to achieve the following scenario: If the class "xyz" is present in the tag, then activate the class "xy" Using ngClass="'xyz', 'xy'" ...

Obtain the content of a clicked item on the following page using NextJs

I am currently working on a nextjs app that displays a list of 10 movies on the homepage, each with a Button / Link that leads to a specific page for that movie where all its content is shown. Initially, I tried adding the movie id to the Link like this: ...

Tips for adjusting the color of the snackbar in Angular

When a user logs out, I am using snackBar to display a message. I want to change the color of the panel from dark grey to another color and tried using the following solution: panelClass: ['danger'] supposed to change the panel color to red ( ...

Steps for displaying API Response in a material-table

In my project, I have a component named List which displays data using mat-cards. When a specific mat-card is clicked, it navigates to another component called home. In this home component, the data from the selected mat-card is displayed within another ma ...