0
votes

I am struggling to deal with handling the response in the client when the server sends back a 401 failed to authenticate status from authentication middleware in a React/Redux Express app.

Simply put, on loading the Dashboard, I am making a request to a protected route:

router.route('/')
  .get(authenticate, controller.get)

Authenticate is the middleware which checks the JWT token:

exports.authenticate = function(req, res, next) {
  const authorizationHeader = req.headers['authorization']
  let token

  if (authorizationHeader) {
      token = authorizationHeader.split(' ')[1]
  }

  if (token) {
    jwt.verify(token, config.secrets.jwt, (err, decoded) => {
      if (err) {
        logger.log(err)
        res.status(401).json({ error: 'Failed to authenticate' })
      } else {
        User.findById(decoded._id)
        .select('-password')
        .exec()
        .then(user => {
          if (!user) {
            res.status(404).json({ error: 'No such user.' })
          } else {
            req.currentUser = user
            next()
          }
        })
      }
    })
  } else {
    res.status(403).json({ error: 'No token provided.' })
  }
}

So if the authentication failed, the returned status is 401. I am trying to work out how to deal with the 401 status gracefully in the middleware and:

  • Delete the localStorage token which is the jwt token
  • Run a function which will delete the authorization header
  • Dispatch a Redux action to set auth back to initial state, which is an isAuthenticated boolean to false, and the user back to an empty object. This is then handled in the application by redirecting the user back to the login page

As mentioned before, I am using thunk middleware to deal with async actions (just no idea how to handle the 401 response)

const configureStore = () => {
  const store = createStore(
    rootReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(thunk)
  )
  return store
}

Thank you :)

UPDATE I was thinking I could use a catch in the action, and then query the res.status code and then clean the token and log user out, but this seems repetitive, I would need to do this for each of the actions that need authentication.

Is there a cleaner way? Code I used below to illustrate:

export const fetchTransactions = () => dispatch => axios.get('/api/transactions')
  .then(transactions => {
    dispatch({
      type: FETCH_TRANSACTIONS,
      payload: transactions
    })
  }).catch(err => {
    if (err.status === 401) {
      localStorage.removeItem('mm-jwtToken')
      setAuthToken(false)
      dispatch(setCurrentUser({}))
    }
  })
1
on failed auth, dispatch a failed auth action right?Chris Hawkes
Exactly, but not sure where exactly to dispatch it fromLe Moi

1 Answers

1
votes

In the end I handled the error in the action creator, catching the error, then calling a handleErr function which:

  • Deleted the local storage token
  • Called a function with false which deletes the authorization header
  • Returned the action that needed to be dispatched in order to remove the user from the Redux store.

    function handleErr (err) { if (err.status === 401) { localStorage.removeItem('mm-jwtToken') setAuthToken(false) return setCurrentUser({}) } }

    export const fetchTransactions = () => dispatch => axios.get('/api/transactions') .then(transactions => { dispatch({ type: FETCH_TRANSACTIONS, payload: transactions }) }, err => { dispatch(handleErr(err)) })