5

I have some trouble with refresh token flow in my React application. I use axios interceptors for handling response with statusCode === 401, refresh my tokens and repeat all requests after that.

Axios interceptors

export const customAxios = axios.create({
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Accept': 'application/json',
    "Content-Type": "application/json",
  }
});

customAxios.interceptors.response.use(
  function(response) {
    return response
  },
  function(error) {
    const errorResponse = error.response;
    if (isTokenExpiredError(errorResponse)) {
      return resetTokenAndReattemptRequest(error);
    }
    return Promise.reject(error);
  }
)
function isTokenExpiredError(errorResponse) {
  if (errorResponse.status === 401) {
    return true
  }
}

let isAlreadyFetchingAccessToken = false;
let subscribers = [];

async function resetTokenAndReattemptRequest(error) {
  const {dispatch, getState} = store;

  try {
    const { response: errorResponse } = error;
    const resetToken = getState().login.refreshToken;
    if (!resetToken) {
      return Promise.reject(error);
    }
    const retryOriginalRequest = new Promise(resolve => {
      addSubscriber(tokens => {
        errorResponse.config.headers.Authorization = 'Bearer ' + tokens.accessToken;
        resolve(axios(errorResponse.config));
      });
    });
    if (!isAlreadyFetchingAccessToken) {
      isAlreadyFetchingAccessToken = true;
      const response = await axios.get(`${API}/auth/refreshtoken`, configHeaders(resetToken))
      if (!response.data) {
        return Promise.reject(error);
      }
      const newTokens = response.data;
      dispatch(refreshTokenSuccess(newTokens))
      isAlreadyFetchingAccessToken = false;
      onAccessTokenFetched(newTokens);
    }
    return retryOriginalRequest;
  } catch (err) {
    return Promise.reject(err);
  }
}

function onAccessTokenFetched(tokens) {
  subscribers.forEach(callback => callback(tokens));
  subscribers = [];
}

function addSubscriber(callback) {
  subscribers.push(callback);
}

And it's works fine! But, I have an issue with refresh functionality when user open new browser tabs. In my application I have 'online' data table, with some delay for repeating request.

Redux-Saga actionChannel for making requests with some delay

export function * vehiclesWatcher () {
  let getVehiclesTask;
  const requestChan = yield actionChannel(GET_VEHICLES_REQUEST);
  while (true) {
    yield take(requestChan);
    getVehiclesTask = yield fork(vehiclesFlow);
    yield take([CLEAR_VEHICLES_REQUEST]);
    yield cancel(getVehiclesTask);
  }
}

vehiclesFlow

function * vehiclesFlow () {
  try {
    while (true) {
      const accessToken = yield select(getToken);
      const searchBy = yield select(result);
      const id = yield select(getId);
      const vehicles = yield call(getAllVehicles, accessToken, id, searchBy);
      yield put ({type: GET_VEHICLES_SUCCESS, payload: vehicles});
      yield delay(5000);
    }
  } catch (error) {

  } finally {
    if (yield cancelled()) {
      yield put({type: CLEAR_VEHICLES_SUCCESS});
    }
  }
}

getAllVehicles

async function getAllVehicles (token, id, searchBy) {
  try {
    const res = await customAxios.post(`${API}/vehicle/getsorted?take=${15}`, 
      JSON.stringify({...searchBy, id }), configHeaders(token));
    return res.data;
  } catch (error) {
    throw error;
  }
}

If the user clicks on a row of the table, a new component will open in a new tab, and a new server request will go.

Redux-Saga action channel for make one request in new browser tab

export function * vehicleWatcher () {
  const requestChan = yield actionChannel(GET_VEHICLE_REQUEST);
  while (true) {
    const { id } = yield take(requestChan);
    yield fork(vehicleFlow, id);
  }
}

This is my parent window.

enter image description here

If I click on table row in the same time when server response was 401, I will get response 401 in the new tab, and my refreshtoken request will be failed

enter image description here

Any idea how to fix this? How pause all sagas when REFRESH_TOKEN_REQUEST/REFRESH_TOKEN_SUCCESS actions are in progress?

Elli Zorro
  • 463
  • 3
  • 19
  • Instead of waiting for the token to expire and then retrying the requests, why not refresh the token before it expires? The token will include its expiry time. – DDD Oct 09 '20 at 08:51
  • The easier way is to refresh the token in advance as Daniel said. Another option, you can try the approach in https://blog.bitsrc.io/complex-app-logic-with-redux-and-redux-saga-write-an-authentication-monitor-2f5672303d7d – Javier Dottori Mar 17 '21 at 13:00
  • I decided to use only axios interceptors for refresh token logic – Elli Zorro Mar 18 '21 at 07:49

0 Answers0