The art of using Axios interceptors for seamless asynchronous logins

Token authentication is being implemented in my application. The access token expires after a certain time and is then refreshed using a refresh token.

Axios is used for API calls, with an interceptor set up to handle 401 responses:

axios.interceptors.response.use(undefined, function (err) {
  if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
    serviceRefreshLogin(
      getRefreshToken(),
      success => { setTokens(success.access_token, success.refresh_token) },
      error => { console.log('Refresh login error: ', error) }
    )
    err.config.__isRetryRequest = true
    err.config.headers.Authorization = 'Bearer ' + getAccessToken()
    return axios(err.config);
  }
  throw err
})

When a 401 response is intercepted, a login is performed along with retrying the original failed request using the fresh tokens. However, there seems to be an issue where the retry happens with the old expired credentials as the then block of serviceRefreshLogin executes later than the getAccessToken() in the interceptor.

The functions getAccessToken() and getRefreshToken() retrieve the stored tokens from the browser's storage mechanisms such as localStorage or cookies.

What steps can be taken to ensure that statements are executed only after a promise returns successfully?

(For more context, refer to this Github issue: https://github.com/mzabriskie/axios/issues/266)

Answer №1

Simply use a different promise approach :D

axios.interceptors.response.use(undefined, function (err) {
    return new Promise(function (resolve, reject) {
        if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
            serviceRefreshLogin(
                getRefreshToken(),
                success => { 
                        setTokens(success.access_token, success.refresh_token) 
                        err.config.__isRetryRequest = true
                        err.config.headers.Authorization = 'Bearer ' + getAccessToken();
                        axios(err.config).then(resolve, reject);
                },
                error => { 
                    console.log('Refresh login error: ', error);
                    reject(error); 
                }
            );
        }
        throw err;
    });
});

If your environment does not support promises, consider using a polyfill like https://github.com/stefanpenner/es6-promise

Alternatively, you could simplify the code by rewriting getRefreshToken to return a promise

axios.interceptors.response.use(undefined, function (err) {

        if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
            return getRefreshToken()
            .then(function (success) {
                setTokens(success.access_token, success.refresh_token);                   
                err.config.__isRetryRequest = true;
                err.config.headers.Authorization = 'Bearer ' + getAccessToken();
                return axios(err.config);
            })
            .catch(function (error) {
                console.log('Refresh login error: ', error);
                throw error;
            });
        }
        throw err;
});

See a demo here: https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview

Answer №2

A more efficient approach would be to handle the token issuance within the request rather than the response. This can prevent unnecessary server calls when the access token has expired. You can refer to this insightful article for more details:

function manageToken() {
  return new Promise((resolve, reject) => {
    return client({
      ...
    }).then((response) => {
      resolve(response);
    }).catch((err) => {
      reject(err);
    });
  });
}

client.interceptors.request.use((config) => {
  let originalRequest = config;
  if (tokenIsExpired && path_is_not_login) {
    return manageToken().then((token) => {
      originalRequest['Authorization'] = 'Bearer ' + token;
      return Promise.resolve(originalRequest);
    });
  }
  return config;
}, (err) => {
  return Promise.reject(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

Display a Popup at 5PM without the need for a page refresh, updating in real-time

After searching extensively online, I was unable to find a suitable solution for my issue. What I am aiming for is to have a popup appear on my page every day at 5:00 PM without the need to refresh the page. If I happen to access the page before 5:00 PM an ...

Typescript version 2 is facing difficulties in resolving an external node module

Lately, I've been experimenting with prototyping using koa and Typescript 2.0. In my simple project, I've configured the tsconfig.json file like this: { "compilerOptions": { "outDir": "./bin/", "sourceMap": true, "no ...

Is there a way to customize the directory that BABYLON.SceneLoader scans for textures?

Is there a way for me to specify the folder where BABYLON.SceneLoader should look for textures when loading a babylon blender file? Here is an example: BABYLON.SceneLoader.Load("","public/js/corredor.babylon", this.engine, function (newScene) { /*... ...

in node.js, virtual machine scripts can import modules using the require function

I am currently developing a command-line interface using node.js that runs an external script > myapp build "path/to/script.js" myapp is a node.js application that executes the script provided as a command-line argument. In simple terms, it performs ...

Error in JavaScript: addition of all numbers not functioning properly within a loop

After attempting to sum all the numeric values within the result[i].quantity array using += or dataset.quantity = 0 + Number(result[i].quantity);, I encountered issues where the console.log was returning either NaN or the value from the last iteration like ...

Leveraging Javascript to access the Google Distance Matrix API

I'm in the process of learning Javascript and decided to build a simple web app to practice my skills. My goal is to incorporate the Google Distance Matrix API into my project so I can calculate the distance between two addresses. To achieve this, I ...

Exploring revisions within the MongoDB database

Currently, I am in the process of designing a MongoDB database to interact with a script that periodically polls a resource and stores the response in the database. The existing structure includes a collection with four fields: id, name, timestamp, and dat ...

Utilize Angular.js or JavaScript to swap out one string with another

I have a JSON dataset and I want to update a specific value using Angular.js or JavaScript. Here is my code snippet: $http({ method:'POST', url:"php/getFilterCodeData.php", data:filterData, headers: { 'Content-Type': &a ...

Tips for extracting the team member field value, which is a classification in Sitefinity 12 web services, through the use of OData and JavaScript

Utilizing ajax to retrieve data from web services in sitefinity, I have been able to successfully retrieve team member information using this specific apiURL: localhost/api/lawyerswebservice/teammembers?$expand=RelatedTeam,PrimaryImage; However, I have e ...

The div remains unchanged when the button is clicked

My webpage is filled with several large div elements. There's a button on the page that, when clicked, should switch to the next div. Despite my efforts, the code I've written doesn't seem to be working as expected. :( This is the HTML st ...

More efficient methods for handling dates in JavaScript

I need help with a form that requires the user to input both a start date and an end date. I then need to calculate the status of these dates for display on the UI: If the dates are in the past, the status should be "DONE" If the dates are in the future, ...

Response from the AJAX server

I need help with handling server-side code that returns either true or false upon checkout fulfillment. The console log displays {"success": true, "message": "Added to db"}. How can I modify the ajax success condition to perform different actions based on ...

A method for determining the quantity of <li> elements within a <ul> container while applying specific conditions

I am currently using document.querySelectorAll('ul > li').length to count the total number of items in my list. However, I am wondering if there is a way to count only those items that meet a specific condition. For example: <ul> < ...

Verify whether the element is capable of being scrolled sideways

Is it possible to add indicators to a div to show users that they can scroll left or right within the content? I need a way to detect if the element can be scrolled to either side, so that I can display the appropriate indicator. For example, when there is ...

How is it that when a function is called with just one parameter, it ends up receiving the entire element?

In my table, I have various item numbers listed with corresponding quantity input fields identified by id="itemNumber". Each line also includes a button that triggers a function (onclick="addItemAndQuantity(itemNumber)") to retrieve inf ...

Sharing Global Variables in Node.js: What's the Best Way to Pass Them into Required Files?

Recently, I decided to organize my gulpfile.js by splitting it into multiple files within a /gulp folder. However, I encountered an issue when trying to pass a variable debug (boolean) into these files to control the behavior of the gulp command being incl ...

Javascript: Accessing a shared variable within the class

Embarking on my JavaScript programming journey filled me with optimism, but today I encountered a challenge that has left me stumped. Within my tutorial project, there exists a class named Model which contains both private and public variables. Of particu ...

Tips for minimizing typing lag in text fields using react-redux

// InputField.jsx import React from 'react' import { useDispatch } from 'react-redux' import { setValue } from '../action/setValue.action' import { Form, FormItem, FormInput } from 'react-blueprint' export const In ...

Creating the property 'label' on a string may lead to an error, especially on objects

I encountered a perplexing error that is giving me trouble. My objective is to change the value in a text field within a form upon clicking a button. However, I keep encountering this error: Cannot create property label on string for one of the instances. ...

Unlock the power of Angular pipes in window.open with URL parameters!

<div class="member-img" onclick="window.open(childEpisode.File_URL | fullPath)"> </div> The fullPath function concatenates the domain part to the relative URL stored in file_URL. However, there seems to be an issue as it i ...