Search code examples
reactjsauthenticationaxiosreact-routerrefresh-token

React axios interceptor doesn't await rejected request


Description

There is a request in a protected route that should be processed only after verification have been processed. Access and refresh token are in cookies and are attached to all necessary requests.

Problem:

After access token is expired, backend returns 400, should process block with refreshing token in axios interceptor, but first comes request from protected route, and then refresh

Screen

requests

Axios interceptor:

    import axios from 'axios';
    import { API } from '@/constants/api';
    import AuthService from '@/services/AuthService';
    
    export const axiosInstance = axios.create({
      baseURL: API.base,
      timeout: 15000,
      withCredentials: true,
    });
    
    axiosInstance.interceptors.request.use(
      async (config) => config,
      async (error) => Promise.reject(error),
    );
    
    axiosInstance.interceptors.response.use(
      async (response) => response,
      async (error) => {
        const config = error?.config;
    
        if (error?.response?.status === 400 && !config?.sent) {
          await AuthService.refreshTokenPair();
    
          return axiosInstance.request(config);
        }
        return Promise.reject(error);
      },
    );
    export default axiosInstance;

Protected route:

      const ProtectedRoute = observer((): JSX.Element => {
      const { isAuth } = userStore;
      const handleUnauthorized = () => {
        userStore.isAuth = false;
      };
    
      useLayoutEffect(() => {
        const verify = async () => {
          const verifyDetails = await AuthService.verifyAccessToken();
    
          const details = await UserService.getUserDetails(verifyDetails?.id);
          if (!details) {
            handleUnauthorized();
            return;
          }
    
          userStore.user = details;
          userStore.isAuth = true;
        };
    
        verify();
      }, []);
    
      if (!isAuth) {
        return <Navigate to={ROUTES.home.path} replace />;
      }
    
      return <Outlet />;
    });
    export default memo(ProtectedRoute);

Solution

  • Resolve by:

        if (error?.response?.status === 401 && config && !config.sent) {
          config.sent = true;
          try {
            await AuthService.refreshTokenPair();
    
            return await axiosInstance.request(config);
          } catch {
            return await Promise.reject(error);
          }
        }
        return Promise.reject(error);
    

    and protected:

          ...
          const verifyDetails = await AuthService.verifyAccessToken();
          if (!verifyDetails) {
            handleUnauthorized();
            return;
          }
          ...
          }, []); // UseEffect deps
          ...