erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
12k stars 2.5k forks source link

log user out when encounter 401 error. (how to do it in clientMiddleware). #1237

Closed xinghengwang closed 8 years ago

xinghengwang commented 8 years ago

background: we use Tokens for authentication. After a while the token expires, so further API calls to the backend will result in a 401 (unauthorized) error. So I would like to dispatch logout if that is the case. (Even though this is very easily doable in the reducers, but the reducers are divided up into different modules, so if I was to do this in the reducer, I would do it all over the place). So I thought the ideal place to handle this would be in the clientMiddleware.js.

so I thought something like this would work:

export default function clientMiddleware(client) {
  return ({dispatch, getState}) => {
    return next => action => {
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }

      const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
      if (!promise) {
        return next(action);
      }

      const [REQUEST, SUCCESS, FAILURE] = types;
      next({...rest, type: REQUEST});

      const actionPromise = promise(client);
      actionPromise.then(
        (result) => next({...rest, result, type: SUCCESS}),
        (error) => {
          next({...rest, error, type: FAILURE});
          if(error && error.status === 401) {
            dispatch(logoutUser());
          }
        }).catch((error)=> {
        console.error('MIDDLEWARE ERROR:', error);
        next({...rest, error, type: FAILURE});
      });

      return actionPromise;
    };
  };
}

However, I noticed that the error object is very different than the error object that is returned by ApiClient.js (error object returned by ApiClient is a superagent error, so it would have error.status).

The error object in the clientMiddleware has a code of "107".

So I was wondering how to best access the original ApiClient's error object in the clientMiddleware.

Thanks

philipchmalts commented 8 years ago

I think in your case you trying to access the error object after it has already been passed to next. If you put your if above the next you can see all of the keys in the error object. In our project we use a separate middleware that checks for 401s to keep the concerns separated. If you add a spread to the error object you will have the status in all async errors. For example

actionPromise.then(
        (result) => next({ ...rest, result, type: SUCCESS }),
        (error) => {
          next({ ...rest, ...error, type: FAILURE });
        }).catch((error) => {
          console.error('MIDDLEWARE ERROR:', error);
          next({ ...rest, error, type: FAILURE });
        });

Then in another middleware you can dispatch actions by different status codes.

export const authInterceptor = ({ dispatch }) => next => action => {
  if (action.status === 401) {
    Promise.resolve(dispatch(refreshToken()).then(() =>
      next(action)
    , (error) => {
      console.log(error);
      dispatch(logout());
    }
    ));
  } else {
    return next(action);
  }
};

Hope this helps!

xinghengwang commented 8 years ago

Agreed. a separate middleware is best solution. thanks.