1

I'm trying to block access to private paths by checking with my express server if a user has logged in prior to allowing access to it, otherwise redirect to login.

I created a private route function that checks with the server. Problem is, i don't know how to get it to wait for the response before deciding which component to route to (private page vs login).

const isAuthed = () => {
  fetch('/api/checkLogin', {
    method: 'GET',
    credentials: 'include'
  })
  .then(response => {
    console.log(response.status == 200);
    return response.status == 200;
  });
}

const PrivateRoute = ({ component: Component, ...rest }) => {
    return (
      <Route
        {...rest}
        render={props =>
          isAuthed() == true ? (
            <Component {...props} />
          ) : (
            <Redirect
              to={{
                pathname: "/login",
                state: { from: props.location }
              }}
            />
          )
        }
      />
    );
  }

This is always Redirecting to login...

Any ideas?

EDIT 1 Here's how PrivateRoute is being used.

const Routing = (props) => (
  <div>
    <Switch>
      <Route exact path='/' component={Home} />
      <PrivateRoute exact path='/profile' component={Profile} />
      <PrivateRoute exact path='/auction_list' component={Auctions} />
    </Switch>
  </div>
)

With react, seems like I have to return something when calling for the Routing component. I can't async call to PrivateRoute then have that async call give a return update.

EDIT 2 I tried using react-redux to store the login data so I don't have to do an async call to the server to auth. However, refreshing the page would wipe out all redux storage so it's as if the user is not logged in after refresh, that's pretty clunky.

I thought of putting the info in session storage. But user won't be able to log out of all machines at once.

I ended up pinging the server from componentWillMount of each of the private components. The private page might flash for a fraction of a second but that's the best I can do so far...Let me know if there's a better way...there must be one.

theNotSoPro
  • 308
  • 3
  • 16
  • Yes. IsAuthed is not returning true. – MrPizzaFace May 27 '18 at 01:43
  • IsAuthed would return true if we wait for the fetch to come back with a response (if a user is authorized) @fabbb – theNotSoPro May 27 '18 at 01:45
  • 1
    isAuthed() is an async call and will return undefined when invoked as a normal method, so `isAuthed() == true` will always be false. And ideal approach would be to make the async call in the parent component and pass `isAuthenticated` as a prop to PrivateRoute component. – Dinesh Pandiyan May 27 '18 at 01:46

2 Answers2

1

You should not be authenticating through the server every time the user visits the site.

react-router's conditional routing only works if the condition can be checked synchronously.

When the user successfully authenticated for the first time, you store a variable indicating the authentication status (with an expiry date if desired) to localStorage.

When the user visits the website again, you will be able to redirect by getting the auth status from localStorage (which is a synchronous operation).

Roy Wang
  • 11,112
  • 2
  • 21
  • 42
  • Yeah, kinda had a feeling I can't check the server on every visit. I'll probably store the auth status in sessionStorage instead. Would you have an idea on the best way to keep sessionStorage updated with the server auth status? For ex: User logs in...goes to another computer, logs out, comes back to first computer, visits a private site...sessionStorage would still say it's authed. – theNotSoPro May 27 '18 at 01:59
  • You can perform an async authentication (after the `PrivateRoute` has been rendered), so user will still see the private page for a brief second before getting redirected (when the API call returns). The other approach is to render a loading page (eg. with a spinner) until the async auth completes, but that's usually not a good UX. – Roy Wang May 27 '18 at 02:07
  • so if i add tomato in localStorage this will allow me to access the protected page !? – Hasan Daghash Aug 04 '18 at 10:21
  • @HasanDaghash yes, but the user will not be able to fetch updated data since the AJAX call will fail with invalid authentication cookies, so the page will contain only empty/outdated data. This is how modern SPAs work, as clients will have the (minified) source code of the page view anyway. – Roy Wang Aug 04 '18 at 12:14
  • i doubt angular has canActivate and it handled properly this part , but in react i'm still exploring the best practice – Hasan Daghash Aug 06 '18 at 13:11
  • @riwu If showing loading page(e.g. with spinner) is not good UX. Then how can I use async authentication after the PrivateRoute has been render? – Rohit Sawai Dec 02 '19 at 12:57
  • @RohitSawai redirect user to login page etc if the async auth failed. – Roy Wang Dec 02 '19 at 13:15
  • @riwu Yes thats when aync auth failed. But when promise is in processing then instead of showing white/blank page, we can show spinner. I think that will be good UX. – Rohit Sawai Dec 02 '19 at 13:30
  • @RohitSawai but most of the time the async auth will succeed, so you are showing a spinner unnecessarily most of the time. It depends on your use case and requirements I guess. – Roy Wang Dec 03 '19 at 01:49
  • @riwu I have used [this answer](https://stackoverflow.com/a/46162585/9282474) but problem with that solution is, componentDidMount() calls once not everytime when user clicks on another link which is private route. – Rohit Sawai Dec 03 '19 at 05:50
0

Letting the user access private routes seems so weird to me. Even if it is just the view. I get they wont have access to any actions when it comes to the database, but it still seems like a flaw. I was also trying to check with the server on every request on my protected route and ended up at this same exact issue. The only way to get around this is to have the actual screen/page make the call and validate the JWT token. I think in my case this will be enough.

zootechdrum
  • 111
  • 1
  • 8