The Angular Http Interceptor is failing to trigger a new request after refreshing the token

In my project, I implemented an HTTP interceptor that manages access token refreshing.
If a user's access token expires and the request receives a 401 error, this function is designed to handle the situation by refreshing the token and re-executing the request with the updated access token.

Below is how the function is called:

return next.handle(request).pipe(catchError((error) => {
  if (error instanceof HttpErrorResponse && error.status === 401) {
    return this.handle401Error(request, next);
  } else {
    return throwError(error);
  }
}));

Here is the implementation of handle401Error method:

handle401Error(request: HttpRequest<any>, next: HttpHandler): any {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      this.auth.refreshAccessToken().then((token: Token) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.access_token);
        return next.handle(this.addToken(request, token.access_token));
      });
    } else {
      return this.refreshTokenSubject.pipe(
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {
            return next.handle(this.addToken(request, token));
          }));
    }
  }

I have followed an article to build this interceptor. The token refreshing process works smoothly except for the line:

return next.handle(this.addToken(request, token.access_token));

This line is supposed to resend the request using the newly obtained valid token but somehow it doesn't trigger.

Answer №1

The dilemma

this.auth.refreshAccessToken() gives back a promise (I assume based on the .then()).

Clarification

In case you are unfamiliar with promises, they are commonly used to handle asynchronous code. Here is a link to the documentation.

The

this.auth.refreshAccessToken().then()
requires a function as an argument. In this scenario, you have utilized an anonymous arrow function (token: Token) => { ... }.

When you execute

return next.handle(this.addToken(request, token.access_token));
, you are within the arrow function. Therefore, you are not directly returning from handle401Error() but rather to .then().

Although .then() returns a value, currently it's not being returned by you.

Your else block demonstrates the correct approach:

return this.refreshTokenSubject.pipe(                          
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {
            return next.handle(this.addToken(request, token)); 
          }));
    }

The resolution

TLDR;

 return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.access_token);
        return next.handle(this.addToken(request, token.access_token));
      }));

Clarification

A quick tip that could simplify things - I recommend using the return type of handle.next() instead of any in determining the return type of handle401Error(), which is

Observable<HttpEvent<any>>
.

You need to return the result of next.handle() within

this.auth.refreshAccessToken().then()
.

There are multiple ways to achieve this, and one Angular/RxJS style stands out.

Promises are similar to observables, and RxJS (v6+) offers a method to convert a promise into an observable. Example:

import { from } from 'rxjs';
const observable = from(promise);

To convert this.auth.refreshAccessToken() into an observable, use:

from(this.auth.refreshAccessToken())

Now that you have an observable, refrain from extracting the value using subscribe since your interceptor should return a final observable subscribed elsewhere.

Instead, leverage pipe, allowing you to apply various operators provided by RxJS. The suitable operator for waiting for the initial observable to emit before returning

next.handle()</code is <a href="https://rxjs.dev/api/operators/switchMap" rel="noreferrer">switchMap</a>.</p>
<p>You'll notice your else block employs this technique:</p>
<pre><code>return this.refreshTokenSubject.pipe(
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {                               
            return next.handle(this.addToken(request, token));
          }));
    }

switchMap() waits for the first observable to emit, passing the value into your callback expecting another observable in return. In your case, replace then() with pipe(switchMap()).

As illustrated in the TLDR section:

 return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.access_token);
        return next.handle(this.addToken(request, token.access_token));
      }));

This solution should address your concern. Feel free to leave a comment if further assistance is needed.

Answer №2

It is not necessary for the handle401Error function to return next.handle(...). This action is already taken care of within the intercept function. Instead, simply include the token in the request as usual and then return null; from the handle401Error function. The catchError function should consistently return null unless a specific custom error needs to be thrown.

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

Looking to retrieve parameters from a route in Angular 7's Router

Encountered an issue with the params in the router configuration. { path: 'profile', component: ProfileComponent, canActivate:[AuthGuard], children: [ { path: 'section/:id/:idarticle', component: WriteArticleCom ...

Using javascript to store HTML tags in a variable

Hey there, I have a quick question. Can someone help me figure out why this code isn't working? let plus = "+" + '<h1>'+"This is a heading"+'</h1>'; When I run the code, the output I get is: +<h1 ...

Ways to retrieve information from a specific key

I'm currently facing a challenge accessing specific data objects that are referenced by keys. In this particular scenario, the "applicant" data is nested within an Event object. My goal is to extract this data and create a new object from it. While I ...

Retrieving Data from Outside Source using AngularJS

Is there a way to retrieve JSON-Text-Stream data from a specific URL (e.g. SOMEURL/ean.php?id=4001513007704)? The returned result typically appears as follows: { "product": { "ean_id": "4001513007704", "title": "Gerolsteiner Mineralw ...

Removing dropdown lists from input forms can be achieved without using jQuery by utilizing vanilla JavaScript

Looking to build a custom HTML/JavaScript terminal or shell. Interested in utilizing an input box and form to interact with JavaScript functions/commands. Unsure of how to eliminate the drop-down menu that appears after previous inputs. Pictured terminal: ...

Which type of element does Youtube utilize for the videos on its own domain - <iframe> or <video>?

Do they have a different method for incorporating their videos? My goal is to utilize the playbackRate property on a non-embedded YouTube video for a Chrome extension. ...

Constantly loading image with Meteor HTTP request

Within my Meteor application, I am attempting to dynamically load a random image from an API which returns JSON data structured like this: { "id":2026 "url": "https:// ... " , "large_url":null, "source_id":609, "copyright":"CC0", "site":"unsplash" } ...

The window.open function is returning a null value after attempting to open the specified

Is there a way to prevent users from opening more than one IFrame window for my application? I have included the following code: <html> <head> <title>Testing Window Opening Limitation</title> <meta http-equiv="Content-Type" cont ...

When sending a POST request in Angular and Node.js, the req.body object is found to be empty {}

Presenting My Service Module import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Items } from "./inventory.model"; import { Router } from "@angular/router"; impor ...

The grid flex end is behaving differently than I anticipated

I am struggling to align two buttons vertically on the right side. Here is my code snippet: const WalletsContainer = () => { return ( <Grid style={{ background: 'red' }} direction={'column'} alignItems={'flex-end'} ...

Tips for concealing subsequent pages and displaying pagination in jQuery ajax response

Is there a way to display pagination based on a limiter in an ajax response? For example, if the limiter is set to 5, only show 10 page links and hide the rest. 1 2 3 4 5 6 7 8 9 10 .. next 11 12 13 14 15.. next I attempted to count the li elements in ...

Difficulty maintaining list formatting in AngularJS and Bootstrap due to ng-repeat functionality

I'm currently working on a project where I need to display content from an array using ng-repeat in Angular. The content is originally in JSON format, but it has been stringified before being added to the array. The problem I am facing is that this c ...

Having issues with validating a form using Yup for a Checkbox input?

My form is built using mui, formik, and yup. If the input fields are empty (e.g. "surname") after clicking the submit button, an error is displayed. However, the issue arises when the checkbox for Terms of Service isn't checked as no error shows up. ...

Ensuring the canvas fits perfectly within its parent div

I am attempting to adjust my canvas to fit inside a div. // Make the Canvas Responsive window.onload = function(){ wih = window.innerHeight; wiw = window.innerWidth; } window.onresize = function(){ wih = window.innerHeight; wiw = window.innerWidth; } // ...

Forced line break at particular point in text

I would love to implement a line break right before the "+" character, either using css styling or through a different method. Is this task achievable? #myDiv{ width: 80% } #myP{ c ...

How can I use jQuery to target elements other than the vertical scrollbar when

Here is how I am utilizing the mouseleave jquery event $(document).ready(function(){ $(document).mouseleave(function(event) { //perform a task }); }); Is there any method to prevent this event from triggering when a user scrolls ...

Receive real-time updates on incoming messages in your inbox

I'm seeking advice on implementing live update messages in my code. Here's what I have so far: <script> function fetch_messages(){ var user_id = "1" // example id $.ajax({ url: "do_fetch.php", t ...

Combine the jQuery selectors :has() and :contains() for effective element targeting!

I am trying to select a list item element that has a label element inside it. My goal is to use the :has() selector to target the list item and then match text within the label using the :contains() selector. Can I achieve this in a single line of jQuery ...

Express Validator: The Art of Isolating Validation Logic

This query is more focused on structuring code rather than troubleshooting bugs or errors. I am currently tackling request body validation, where the JSON structure looks like this: { "title": "Beetlejuice", "year&qu ...

Node.JS using Express: Issue : encountering EADDRINUSE error due to address being already in use

Currently, I am in the process of developing a CRUD API with Node.js and Express. Everything was going smoothly until today when a new error message popped up. It appears that I can only use a TCP Port once. Whenever the server is stopped and restarted, I ...