Managing Angular 6 HTTP Interceptor for Refresh Token in case of 401 Unauthorized Error

After implementing my AuthInterceptor, I am facing an issue where the handle401Error method does not receive any response when calling this.authService.refreshToken().pipe() upon encountering a 401 error.

AuthInterceptor Implementation

import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap, switchMap } from 'rxjs/operators';
import { AuthService } from '../services/auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  isRefreshingToken:boolean = false;
  constructor(public authService: AuthService, private injector: Injector) {}

    addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
        return req.clone({ setHeaders: {
          Authorization: 'Bearer ' + token,
          'Content-Type': 'application/json'
       }})
    }

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

    request = this.addToken(request, this.authService.getToken())
    return next.handle(request).pipe (
      catchError((error: Error) => {
        if(error instanceof HttpErrorResponse){
          console.log('err', error);
          switch(error.status) {
            case 401:
            this.handle401Error(request, next)
            default:
            return throwError(error);
          }

        } else {
          return throwError(error);
        }
      })
    );
  }

  handle401Error(request: HttpRequest<any>, next: HttpHandler){
    console.log('handle 401');
    if(!this.isRefreshingToken) { 
      this.isRefreshingToken = true;

      return this.authService.refreshToken().pipe(
        switchMap((newToken: string) => {
          console.log('newToken', newToken) 
            if (newToken) { 
              return next.handle(this.addToken(request, newToken));
            }

        }),
        catchError((error) => {
          console.log('error', error) 
          return throwError(error)
        }),
        tap(data => console.log(data))
      )
    }
  }
}

AuthService Refresh Token

refreshToken(): Observable<string> {
    console.log('refreshToken');
    let token = 'xxx-xxx-xx';

    return of(token).pipe(delay(1000));
}

Answer №1

In addition to addressing the missing return in 'case 401', below is my suggested approach to handling it.

The main concept is to retry the entire observable once the new refresh token is received. The request is patched inside an observable so that with each retry, it will obtain the new token from the auth service.

import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { Observable } from 'rxjs';
import { catchError, retryWhen, switchMap, tap } from 'rxjs/operators';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private retryRequest = Symbol('reload');
  private refreshToken = this.authService.refreshToken()
    .pipe(
      tap(newToken => {
        if (newToken) {
          throw this.retryRequest;
        }
      }),
      share(),
    ) as Observable<any>;

  constructor(public authService: AuthService) { }

  private addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
    return req.clone({
      setHeaders: {
        Authorization: 'Bearer ' + token,
        'Content-Type': 'application/json'
      }
    });
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const request$ = new Observable<HttpRequest<any>>(observer => {
      observer.next(this.addToken(request, this.authService.getToken()));
      observer.complete();
    });

    return request$.pipe(
      switchMap(req => next.handle(req)),
      catchError((error: Error) => {
        if (error instanceof HttpErrorResponse) {
          switch (error.status) {
            case 401:
              return this.refreshToken;
            default:
              throw error;
          }
        } else {
          throw error;
        }
      }),
      retryWhen(err$ =>
        err$.pipe(
          tap(err => {
            if (err === this.retryRequest) { return; }
            throw err;
          })
        )
      )
    );
  }
}

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

Retrieve the data from a JSON file using Angular 4

I have a JSON data structure that looks like this: [{"id": "ARMpalmerillas07", "type": "GreenHouse","act_OpenVentanaCen": {"type": "float", "value": 0, "metadata": {"accuracy": {"type": "Float", "value": "07/02/2018 13:08 : 43 "}}}, "act_OpenVentanaLatNS" ...

Creating Instances of a Subclass from an Object in TypeScript

Here is the code snippet I am working with: class MyClass { name: string = "myname"; constructor(public action: string) { } } let obj1: MyClass = { action: "act1" }; The code does not compile and the error displayed pertains to the last line: P ...

bringing in the DefinitelyTyped library for Angular

I am encountering a similar issue as described in this post => Finding the correct import for a third party DefinitelyTyped module In my Angular (TS) project, I am attempting to integrate VanillaTilt. I have used this index.d.ts and saved it in the sam ...

Enhancing an Angular 8 directive to accept negative decimal numbers using RegExp

I've implemented a directive like the one below: import {Directive, ElementRef, HostListener, Input} from '@angular/core'; @Directive({ selector: '[signedNumeric]' }) export class SignedNumericDirective { private rege ...

How to handle multiple formData input in NestJS controller

How can I create a controller in Nest.js that accepts two form-data inputs? Here is my Angular service code: public importSchema(file: File, importConfig: PreviewImportConfig): Observable<HttpEvent<SchemaParseResponse>> { const formData = ...

Telerik Kendo Grid in Angular not showing data beyond the initial level

We have implemented the Kendo Grid in our Angular 5 project. Within our HTML code, we have successfully used kendo-grid-column with the field "Property-A.Code". However, when attempting to use kendo-grid-column with the field "Property-B.Property-B-One.C ...

A guide on updating an Observable with a value retrieved from another Observable

Struggling to find a way to update the result of one Observable with the result of another. Can anyone provide guidance on how to do this without nested subscriptions and return the resulting Observable for later use? this._http.get<JSON>(url, { pa ...

The functionality of Angular router.navigate is not supported within Material Autocomplete

Currently, I am working on developing an autocomplete feature for a search functionality that allows users to look up products and navigate to a single product page by clicking on a specific product with its ID in the URL. To implement this, I am using An ...

The Angular Router is continuing to show the HomeComponent upon navigation, rather than just displaying the ChildComponent

Lately, I've been diving into Angular and attempting to create a github search application using the github api. However, I've encountered some roadblocks with routing and data passing. My goal is for the user to land on a page like /user/userID ...

Managing animations with multiple components in Angular 2+

I am currently developing an Angular application that will utilize a series of Modals in a wizard-style setup. For this project, I am utilizing the Angular-cli tool. Below is the code snippet showing how I have set up my animations: animations:[ t ...

What is the process for converting this lambda type from Flow to TypeScript?

Currently, I am in the process of converting the below code snippet from Flow to TypeScript let headAndLines = headerAndRemainingLines(lines, spaceCountToTab), header: string[] = headAndLines.header, groups: string[][]= headAndLines.groups, ...

Disconnecting a Metamask Wallet in an Angular application: A Step-by-

I need assistance with disconnecting a Metamask wallet using web3 in Angular. //this is my wallet connection code async connectWallet() { const accounts = await this.ethereum.request({ method: 'eth_requestAccounts', }); this.selectedAddress ...

A more efficient method for querying documents based on ids that are not in a given list and then sorting them by a specific publish date

After implementing the code provided below, I noticed that the performance tests indicate each request takes a second or longer to complete. My goal is to enhance this speed by at least 10 times. The bottleneck seems to be caused by the NOT operator resu ...

Tips on using checkbox values to filter listing rows in Angular

Question: I have a Filter button with options to filter the listing rows. How can I use this method to filter the values? <button mat-raised-button [matMenuTriggerFor]="filter">Filter</button> <mat-menu #filter="matMenu" yPosition="belo ...

What is the most efficient way to update data multiple times by mapping over an array of keys in a react hook?

My question might not be articulated correctly. I'm facing an issue with dynamically translating my webpage using Microsoft's Cognitive Services Translator. I created a react hook for the translator, which works well when I need to translate a si ...

Angular making API calls in Nginx fails to resolve Docker service names

I currently have an Angular application running within a NginX container in Docker. The Angular app is able to successfully make REST calls to another container (a Spring Boot API in a separate Docker container) using the server name localhost. server: st ...

Testing useEffect with React hooks, Jest, and Enzyme to add and remove event listeners on a ref

Here is a component I've been working on: export const DeviceModule = (props: Props) => { const [isTooltipVisible, changeTooltipVisibility] = useState(false) const deviceRef = useRef(null) useEffect(() => { if (deviceRef && dev ...

Must run the angular code in a sequential order

I need to run the code in a specific order; first the foreach loop should be executed, followed by a call to the getHistory() method. Your assistance is greatly appreciated. const execute = async()=>{ await this.currentContent.forEach(async ...

Blank webpage caused by ngx TranslateService

Every time I attempt to translate Angular components using ngx-translate/core by utilizing the translateService in the constructor, the page appears blank. This is my appModule : import { NgModule } from '@angular/core'; import { BrowserModule } ...

Is it considered good practice in Angular testing to use screen.getByText("With static text")?

As I consider selecting elements based on their text content using screen.getByText, a question arises regarding the reliability of this method. If the text changes, the test will fail, but that might not necessarily indicate a problem with the component&a ...