Protected Routes React Router 4 not working with auth state stored in Redux

react protected routes hoc
react router authentication
react router login redirect
react-router token authentication
route protection
react authentication best practices
reach router protected route
react route middleware

I am trying to make an authenticated route in React Router v4 as per this example. Showing the code for posterity:

function PrivateRoute ({component: Component, authed, ...rest}) {
  return (
    <Route
      {...rest}
      render={(props) => (!!authed)
        ? <Component {...props} />
        : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
    />
  )
}

My authentication state (authed), which is initialized as an empty object at the reducer, is derived from a Redux store. This is how my App.js looks like:

class App extends Component {
  componentDidMount() {
    const token = localStorage.getItem("token");
    if (token) {
      this.props.fetchUser();
    }
  }

  render() {
    return (
      <Router>
        <div>
          <PrivateRoute authed={this.props.authed} path='/dashboard' component={Dashboard} />
          />
        </div>
      </Router>
    );
  }
}

The problem is that the authed state starts as undefined and then, once the Router component is mounted, it updates the state to true. This is however a bit late, because the user would be already redirected back to the login page. I also tried to replace the componentDidMount() lifecycle method, with the componentWillMount() but that did not fix the problem either.

What strategies would you suggest?

UPDATE 1: The only way I get around this is by testing for the authed state before returning the <Route /> component such as this:

  render() {
    if (!!this.props.authed) {
      return (
        <Router>
      <div>
      ...

UPDATE 2: I am using Redux Thunk middleware to dispatch the action. The state is being passed as props correctly - I am using console.log() methods inside the PrivateRoute component to verify that the state mutates correctly. The problem is of course that it is mutating late, and the Route is already redirecting the user.

Pasting code of reducer and action...

Action:

export const fetchUser = () => async dispatch => {
  dispatch({ type: FETCHING_USER });
  try {
    const res = await axios.get(`${API_URL}/api/current_user`, {
      headers: { authorization: localStorage.getItem("token") }
    });
    dispatch({ type: FETCH_USER, payload: res.data });
  } catch (err) {
    // dispatch error action types
  }
};

Reducer:

const initialState = {
  authed: {},
  isFetching: false
};
...
    case FETCH_USER: // user authenticated
      return { ...state, isFetching: false, authed: action.payload };

I had the same problem and from my understanding your update #1 should be the answer. However upon further investigation I believe this is an architectural problem. The current implementation of your Private route is dependent on the information being synchronous.

If we think about it pragmatically the ProtectedRoute essentially returns either a redirect or the component based on the state of our application. Instead of wrapping each Route with a component we can instead wrap all the routes in a component and extract our information from the store.

Yes it is more code to write per protected route, and you'll need to test if this is a viable solution.

Edit: Forgot to mention another big reason this is an architectural problem is if the user refreshes a page which is protected they will be redirected.

UPDATE Better solution: On refresh if they are authenticated it will redirect to their target uri https://tylermcginnis.com/react-router-protected-routes-authentication/

Solution 1

//You can make this a method instead, that way we don't need to pass authed as an argument
function Protected(authed, Component, props) {
  return !!authed
    ? <Component {...props} />
    : <Redirect to='/login' />
}

class AppRouter extends React.PureComponent {
  componentDidMount() {
    const token = localStorage.getItem("token");
    if (token) {
      this.props.fetchUser();
    }
  }

  render() {
    let authed = this.props.authed

    return (
      <Router>
          <Route path='/protected' render={(props) => Protected(authed, Component, props)} />
      </Router>
    )
  }
}

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <AppRouter />
      </Provider>
    )
  }
}

Solution 2 Or we can just check for each component (yes it's a pain)

class Component extends React.Component {
  render() {
    return (
      !!this.props.authed
        ? <div>...</div>
        : <Redirect to='/' />
    )
  }
}

React tips, We have many ways to protect routes in React, in a recent post I showed how to use React tips — How to protect routes for unauthorized access with React Router v4 Ok, now we are going to simulate a central state management like Redux Action Creators, so add a fake object to simulate the ajax call for authentication: I'm using react router v4 with redux and i want to make use of private and protected route to redirect if user is not logged in. i have this Routes component: class Routes extends Component { re

Theoretically, you need to get a promise from the NODE API call which you are not getting right now. You need to make architectural changes. I suggest you use redux-promise-middleware this is a redux middleware. I have a sample project in my github account. Where you will get notified if your call to this.props.fetchUser() is completed or not, based on that using Promise you can handle this async problem you have. Go to that repo if need help ask me.

protected routes issue switching from react-router-redux · Issue #210 , Protected Routes React Router 4 not working with auth state stored in Redux - reactjs. Before we even go about creating our protected routes, we’ll need a way to figure out if the user is authenticated. Because this is a tutorial about React Router protected routes and not about authentication, we’ll use a dummy object to mock our auth service.

if your App component is connected to the redux store, you probably mean this.props.authed instead of this.state.authed

My authentication state (authed), which is initialized as an empty object at the reducer, is derived from a Redux store

So you are comparing empty object with true here: (props) => authed === true? Why don't you initialize it with a false?

And are you sure the action this.props.fetchUser is switching the state to true?

Maybe you better also post your action and reducer file

React Router Create Protected Route, I am in progress of migrating my app to from react-router-redux -to Currently I have certain routes that are protected(requires authed users) i.e Sign up for GitHub is saved to auth in redux store(which is persisted to localStorage). component on every other page refresh access the auth state before it  Protected routes are an important part of any web application. In this video we'll break down the "Redirects (Auth)" example on the React Router (v4) documentation to learn how to create

The same problem was happening with me, I am using a temporary hack to solve it by storing an encrypted value inside localStorage and then decrypting it in my PrivateRoute component and checking if the value matches.

action.js

localStorage.setItem('isAuthenticated', encryptedText);

PrivateRoute.js

if (localStorage.getItem('isAuthenticated')) {
const isAuth = decryptedText === my_value;
return (
     <Route
        {...rest}
        render={(props) =>
           isAuth ? <Component {...props} /> : <Redirect to="/login" />
        }
     />
  );
} else {
      return <Redirect to="/login" />;
   }

Since localStorage is faster, so it is not unnecessarily redirecting. If someone deletes localStorage they will simply be redirected to /login Note: It is a temporary solution.

How to Router Redirect After Login, React Router 4.x has the functionality to create private routes. React Redux is for managing states ( data-state and UI-state ) in single page application (SPA). connect } from 'react-redux' const PrivateRouteHandler = ({ component, auth, Everything is simple to grab if you are not new to javascript. $ create-react-app react-protected-routes I will create all components on the same file App.js just for simplicity, and the next thing to do is import some components: Install the react-router-dom:

React Router 3 · GitBook, React uses Redux's state for maintaining state throughout the app. The purpose of the state is to keep your application state synchronized with the Redux store. token in the application state and redirect the user to protected routes. For auth.​js. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export const LOGIN  Well, last weekend I wanted to dig into some good old React without fancy stuffs like Redux-Saga. So I started a side project to create a tiny boilerplate with nothing more than Create React App to implement the authentication flow with Strapi, a Node.js framework with an extensible admin

Usage with React Router, However, here are some common auth problems onEnter does not solve: Decide authentication/authorization from redux store data (there are some Instead, we can use redux-auth-wrapper to protect React Router 3 routes with auth checks to if the user is authenticated or not authenticatedSelector: state => state.user ! #Usage with React Router. So you want to do routing with your Redux app. You can use it with React Router. Redux will be the source of truth for your data and React Router will be the source of truth for your URL. In most of the cases, it is fine to have them separate unless you need to time travel and rewind actions that trigger a URL change.

How to build a React Hooks front-end app with routing and , Redux will be the source of truth for your data and React Router will be the source of truth for your URL. #Installing React Router We will wrap <Router /​> in <Provider /> so that route handlers can get access to the store . <​VisibleTodoList /> 's mapStateToProps() , which is still bound to the state and not to the URL. Server routes where matchRoutes is called. Add routes where the authorization check takes place. Render routes takes the mapped routes from the above file and does the component rendering. Notice it's calling renderRoutes, which is from react-router-config, but a reimplementation of it.

Comments
  • How are you getting the values for 'authed', have you tried to investigate why authed call takes long, can you share that code.
  • @alowsarwar it's an Axios call to a nodejs server that returns a Passport user object (deserialized from jwt token)
  • @James Can you tell me about the UPDATE 1 solution, where are you checking (!!this.props.authed)? I am having the same problem.
  • @Arnab I am testing that the authed state exists, before rendering the <Route /> components. In other words, to answer your question, I am checking it in my routes file.
  • @James Okay, thanks. I will try your solution in the meantime I tried to solve it with a different approach. It is not the most preferred method. But now the problem is not happening. You can check my answer, I have posted it below.
  • Not sure I understand. I am using Redux Thunk to dispatch these actions.
  • Yes I have modified the code as I pasted the example for the other SO answer.