axios: Interceptors - how to prevent intercepted messages from resolving as error

I’m trying to make an interceptor for 401 responses that result from expired token. Upon interception I want to login and retry the requests with the new token. My problem is that login is also done asynchronously, so by the time the retry happens, the original promises reject. Is there a way around that? Here’s my code:

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

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 33 (2 by maintainers)

Most upvoted comments

You could do something like this:

axios.interceptors.response.use(function (response) {
  return response;
}, function (error) {

  const originalRequest = error.config;

  if (error.response.status === 401 && !originalRequest._retry) {

    originalRequest._retry = true;

    const refreshToken = window.localStorage.getItem('refreshToken');
    return axios.post('http://localhost:8000/auth/refresh', { refreshToken })
      .then(({data}) => {
        window.localStorage.setItem('token', data.token);
        window.localStorage.setItem('refreshToken', data.refreshToken);
        axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.token;
        originalRequest.headers['Authorization'] = 'Bearer ' + data.token;
        return axios(originalRequest);
      });
  }

  return Promise.reject(error);
});

Sorry for the delay, I am just seeing this issue.

You need to make a return statement from your interceptor which will keep the Promise alive. More or less change your refreshLogin to return a Promise and then return that from your interceptor.

Or if you can’t refactor refreshLogin you can wrap it in a Promise.

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

The console.log returns undefined.

// for multiple requests
let isRefreshing = false;
let failedQueue = [];

const processQueue = (error, token = null) => {
  failedQueue.forEach(prom => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  })
  
  failedQueue = [];
}

axios.interceptors.response.use(function (response) {
  return response;
}, function (error) {

  const originalRequest = error.config;

  if (error.response.status === 401 && !originalRequest._retry) {
      
      if (isRefreshing) {
        return new Promise(function(resolve, reject) {
          failedQueue.push({resolve, reject})
        }).then(token => {
          originalRequest.headers['Authorization'] = 'Bearer ' + token;
          return axios(originalRequest);
        }).catch(err => {
          return Promise.reject(err);
        })
      }

    originalRequest._retry = true;
    isRefreshing = true;

    const refreshToken = window.localStorage.getItem('refreshToken');
    return new Promise(function (resolve, reject) {
       axios.post('http://localhost:8000/auth/refresh', { refreshToken })
        .then(({data}) => {
            window.localStorage.setItem('token', data.token);
            window.localStorage.setItem('refreshToken', data.refreshToken);
            axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.token;
            originalRequest.headers['Authorization'] = 'Bearer ' + data.token;
            processQueue(null, data.token);
            resolve(axios(originalRequest));
        })
        .catch((err) => {
            processQueue(err, null);
            reject(err);
        })
        .then(() => { isRefreshing = false })
    })
  }

  return Promise.reject(error);
});

Demo project Gist link

Is this issue resolved? I am using 0.15.2 and running into this weird problem

That was with 0.12.0. Now that I updated to 0.13.1, err has become a string with call stack.

Error: Request failed with status code 401 at createError (eval at <anonymous> …

I solved it, the 401 doesn’t have cors. If any one using Laravel with Laravel Cors Package then here is the solution and you will have error.response available

https://github.com/barryvdh/laravel-cors/issues/89

I used @geocine example from Oct, 5th 2016. I am using Vue.js 2.0 my solution was to watch the token for changes in the Vuex if it changed then to update anything that changed. I.E. if someone wanted to like a post but the JWT was expired, it intercepts the request and runs it again once the JWT has been refreshed.

Vue.axios.interceptors.request.use((config) => {
    if (store.state.auth) {
      config.headers.common['Authorization'] = 'Bearer ' + store.state.token
    }
    return config
  }, function (error) {
    // Do something with request error
    return Promise.reject(error)
  })
Vue.axios.interceptors.response.use((response) => {
    return response
  }, function (error) {
    let originalRequest = error.config
    console.log(originalRequest)
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true
      Vue.axios.post('/users/token', null).then((data) => {
        store.dispatch('authfalse')
        store.dispatch('authtruth', data.data)
        originalRequest.headers['Authorization'] = 'Bearer ' + store.state.token
        return Vue.axios(originalRequest)
      })
    }
    // Do something with response error
    return Promise.reject(error)
  })

This is also an issue for me - v.0.15.3 - the error is just a callstack error string

I am pretty sure it should be error.response.status you could check out the error object if it has those properties. I don’t know why it doesn’t work on your end if you only have that isolated sample code similar to what you have posted above.

I had the problem, that error was undefined in a response interceptor and it was caused by not resolving the promise for config in a token refresh request interceptor, if the token refresh failed.

Faulty code in request interceptor:

axios.interceptors.request.use(config => this.dispatch('refreshAuthToken').then(() => {
  // do stuff to set Authorization header
  return Promise.resolve(config);
}))

Fixed code:

axios.interceptors.request.use(config => this.dispatch('refreshAuthToken').then(() => {
  // do stuff to set Authorization header
  return Promise.resolve(config);
// ensure we resolve config in error case
}), () => Promise.resolve(config));

Yes I am using the axios with Vuejs App, This is how I did

/* global API_URL */
window.axios = require('axios').create({
  baseURL: API_URL,
  timeout: false,
  params: {} // do not remove this, its added to add params later in the config
})

// Add a request interceptor
/* global window axios */
axios.interceptors.request.use(function (config) {
  /* global window Store */
  let token = Store.get('jwt.token')
  let location = Store.get('location')

  // console.log(location.id, location)
  if (token) {
    config.headers.common['Authorization'] = 'Bearer ' + token
  }
  // Append location id for every post/get request
  if (location) {
    if (config.data) {
      config.data.location_id = location.id
    }
    else if (config.params) {
      config.params.location_id = location.id
    }
  }

  return config
}, function (error) {
  // Do something with request error
  return Promise.reject(error)
})

axios.interceptors.response.use((response) => response, (error) => {
    console.log(error.config)
})

In the response interceptor rejection function, the error.response.status is returned correctly in the latest version (0.15.3).

Here, error.response return undefined in 0.14.0 version.

Based on this sample: https://github.com/axios/axios/issues/450#issuecomment-247446276

I have used this version for refreshing token - as is described on post above. This interceptor is created for prevent refresh token repeatly if it is created more request and you probable want to call this operation only once.

This is typescript version but I think it is very similiar to js version.

export default class AuthorizationHelper {

	authTokenRequest: Promise<any>;

	getNewAccessToken() {

		const refreshToken = window.localStorage.getItem("refreshToken");

		if (!this.authTokenRequest) {
			this.authTokenRequest = this.refreshToken(refreshToken);
			this.authTokenRequest.then(response => {
				this.resetGetAccessTokenRequest();
			}).catch(error => {
				this.resetGetAccessTokenRequest();
			});
		}

		return this.authTokenRequest;
	}

	resetGetAccessTokenRequest() {
		this.authTokenRequest = null;
	}

	refreshToken(refreshToken: string): Promise<any> {

		return axios.post('/api/token/refresh',
			{
				refreshToken: refreshToken
			});
	}

	registerAxiosInterceptor() {
		axios.interceptors.response.use((response) => {
			return response;
		}, err => {
			const error = err.response;

			if (error.status === 401 && error.config && !error.config.__isRetryRequest) {

				return this.getNewAccessToken().then(response => {
					error.config.__isRetryRequest = true;

					//set new access token after refreshing it
					axios.defaults.headers.common["Authorization"] = `Bearer ${response.access_token}`;
					error.config.headers["Authorization"] = `Bearer ${response.access_token}`;

					return axios(error.config);
				}).catch(error => {

					//refreshing has failed => redirect to login
					//clear cookie (with logout action) and return to identityserver to new login
					//(window as any).location = "/account/logout";

					return Promise.reject(error);
				});
			}

			return Promise.reject(error);
		});
	}
}

I was able to adapt @rlambertsen example. Worked fine.

is this solved ? im using version 0.15.3 but error.response is undefined. i try to handle 401 error. btw im using “Custom instance”