1

folks. I'm learning how to integrate React with Express using React Router, and I've run into a problem with authenticating users. I'm trying to use a higher order component to conditionally render a protected route based on a user's authorization status.

const ProtectedRoute = ({ component: Component, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props => {
        if (!AUTHORIZED) {
          return <Redirect to="/login" />;
        }
        return <Component {...props} />;
      }}
    />
  );
};

The problem I'm having is in the if (!AUTHORIZED) statement. I'm using Passport to handle authentication on the Express server side, and I have an endpoint set up for retrieving user information and authorization status, but I can't figure out how to get access to that data before the page renders. If I was using a class component instead of a functional component, (learning hooks also), I think I could get the data with the componentWillMount lifecycle method, but I read that's bad practice. Any ideas on how I could move forward from here would be much appreciated!

***edit*** A couple of things I tried to get this working... I tried adding an authorization module to fetch the data for me.

class Auth {
  constructor() {
    this.authenticated = false;
  }

  async isAuthenticated() {
    console.log("hitting auth route");
    await fetch("/api/auth")
      .then(res => res.json())
      .then(json => {
        if (json.error) {
          this.authenticated = false;
        }
        this.authenticated = true;
      });
    return this.authenticated;
  }
}

export default new Auth();

I import the module and plug auth.authenticated() in place of the placeholder AUTHORIZED. This function gets skipped, because it's asynchronous, and the redirect will always occur. So I need to add await to auth.authenticated(). But now I need to have async further up the chain, so I foolishly add async in front of props, as such: render={async props => { So now It's trying to render a promise object instead of a component, and we get the error Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead. Promises all the way down.

LuosRestil
  • 169
  • 3
  • 12
  • how is the data getting into your component? Isn't it coming in as a prop? are you making a request to an api and where are you making that request. Please indicate this details in your question. – rotimi-best Mar 07 '20 at 00:59
  • That information isn't in the post because that literally IS the question. The data is not currently getting into my component. It's not coming in as a prop, because I don't think it can be. I would like to make a request to an API, but I can't figure out how to make that work. – LuosRestil Mar 07 '20 at 01:04
  • If all you need is fetching data in hooks then there are plenty of resources available out there: Here is one https://stackoverflow.com/a/53219430/8817146 – rotimi-best Mar 07 '20 at 01:16
  • what is the response from your express backend when a user signs in? is there a token in your response? – Firealem Erko Mar 07 '20 at 01:22
  • The only response I have when a user logs in is a message saying they logged in successfully. I'm using "cookie-session", so when they log in, it stores a session cookie in local storage. The real heart of the issue is that the component loads before I can send a request to my authorization endpoint. It's as though I need a way to make the request from within the ProtectedRoute component, but I don't see how that's possible. There are a few tutorials on this subject, but all of them use fake authorization, so I haven't seen any examples of how this actually works. – LuosRestil Mar 07 '20 at 01:49
  • I beleive you should have unprotected login route , then the protected routes will be using the response from the login(after you save it in session/local storage) endpoint. this is a common approach. – Firealem Erko Mar 07 '20 at 02:15

1 Answers1

3

Answering this in case anyone runs into a similar issue... The first solution I had was based on Firealem Erko's comment. On login, I saved a variable with the user's ID to local storage and referenced that in my component. This was a good first solution, but later was improved by something rotimi-best mentioned in his comment. It turns out you can indeed pass props to these components, which I did not realize in my inexperience. So that is now the way I'm doing it. The final solution is as follows:

const ProtectedRoute = ({
  component: Component,
  logged,
  setLogged,
  ...rest
}) => {
  return (
    <Route
      {...rest}
      render={props => {
        if (!logged) {
          return (
            <Redirect
              to={{
                pathname: "/login",
                state: { flashInfo: "Please log in to continue." }
              }}
            />
          );
        } else {
          return <Component {...props} logged={logged} setLogged={setLogged} />;
        }
      }}
    />
  );
};

And here's the parent component where I'm passing in the props:

function App() {
  let [logged, setLogged] = useState(false);

  useEffect(() => {
    if (window.localStorage.getItem("qrs")) {
      setLogged(true);
    } else {
      setLogged(false);
    }
  }, []);

  return (
    <div className="App">
      <BrowserRouter>
        <Nav logged={logged} setLogged={setLogged} />
        <Switch>
          <ProtectedRoute
            exact
            path="/dashboard"
            component={Dashboard}
            logged={logged}
            setLogged={setLogged}
          />
          <Route
            path="/register"
            exact
            render={props => <Register {...props} logged={logged} />}
          />
          <Route
            path="/login"
            exact
            render={props => (
              <Login {...props} logged={logged} setLogged={setLogged} />
            )}
          />
        </Switch>
      </BrowserRouter>
    </div>
  );
}

Thanks to all the commenters for their suggestions!

LuosRestil
  • 169
  • 3
  • 12