-1

I'm using React with Node & express and authenticating with passport and passport local, I want to know that how can I protect a route in React, I mean I want to show a component only if user is authenticated and otherwise I want it to redirect on login page.

In my case I want to protect Notes route, The method I'm trying is not working at all...

My React Code

import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import axios from 'axios';

import Register from './Components/Register';
import Login from './Components/Login';
import Notes from './Components/Notes';


function App () {


  //Function to check authentication from server
  const checkAuth = () => {
    axios.get('http://localhost:8000/notes', { withCredentials : true })
    .then(res => res.data)
  }


  return (
    <Switch>
      <Route exact path='/' component={Register} />
      <Route exact path='/login' component={Login} />

      <Route exact path='/notes' render={() => {
         return checkAuth()? (
             <Notes />
           ) : (
             <Redirect to="/login" />
           )
      }} />
    </Switch>
  );
};

export default App;

And My server side code

//Notes Page
router.get('/notes', (req, res) => {
    if(req.isAuthenticated()) {
        res.send(req.user.email);
    }else{
        res.send(false);
    }
};
  • Does this answer your question? [How to implement authenticated routes in React Router 4?](https://stackoverflow.com/questions/43164554/how-to-implement-authenticated-routes-in-react-router-4) – Brian Thompson Nov 11 '20 at 14:35

4 Answers4

0

Replace your code below

<Route exact path='/notes' render={() => {
         checkAuth()? (
             <Notes />
           ) : (
             <Redirect to="/login" />
           )
      }} />

With this code please

<Route exact path="/notes">{checkAuth ? <Notes /> : <Redirect to="/login" />}</Route>
Ozgur Sar
  • 1,598
  • 2
  • 6
  • 18
  • it show blank page then, mean'a App component not even detecting this route – Yousuf Kalim Nov 11 '20 at 15:55
  • I didn't add the sections that @steff_bdh mentioned above. It should work with useState and useEffect hooks – Ozgur Sar Nov 11 '20 at 15:58
  • that is working but only first time, when I refresh the page then automatically state's value changes to false and it redirects to login – Yousuf Kalim Nov 11 '20 at 16:01
  • You can persists logged in state by storing a value in localstorage – Ozgur Sar Nov 11 '20 at 16:03
  • For example; inside checkAuth() function, you can first check for the localstorage value. If it is set and true then you can set the authenticated state to true. – Ozgur Sar Nov 11 '20 at 16:10
  • yes this can also work, but I was wondering if there's an easy method to do that, because storing data to local storage and deleting it after logout is a pain. – Yousuf Kalim Nov 11 '20 at 16:24
0

checking login status via "server request" takes time! Consider then to create a Class with state!, i'll give you an example below:

import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import axios from 'axios';

import Register from './Components/Register';
import Login from './Components/Login';
import Notes from './Components/Notes';


class App extends Component {

    state = {
        login: false
    }

    componentDidMount() {
        //Function to check authentication from server
        this.checkAuth()
    }

    checkAuth = () => {
        // API request to check if user is Authenticated
        axios.get('http://localhost:8000/notes', { withCredentials : true })
        .then( res => {
            console.log(res.data);
            // API response with data => login success!
            this.setState({
                login: true
            });
        });
    }


    render() {

        let protectedRoutes = null;
        if(this.state.login === true) {
            // if login state is true then make available this Route!
            protectedRoutes = <Route exact path='/notes' component={Notes} />
        }

        return (
            <Switch>
                <Route exact path='/' component={Register} />
                <Route exact path='/login' component={Login} />
                { protectedRoutes }
            </Switch>
        );
    }
};

export default App;

I hope this example will be useful to you.

mfiore
  • 310
  • 2
  • 15
  • Yes this is working fine but only first time, when I refresh the page then automatically state's value changes to false, – Yousuf Kalim Nov 11 '20 at 15:40
  • At refresh run again => componentDidMount then it will login again!, if you want the user to stay logged in you can implement different logics... I don't know how your backend works, but usually the server returns you a token that you can go save in the front-end and check if the token is still valid without calling the server every time! if your server follows this logic you could use redux or hooks for better performance. – mfiore Nov 12 '20 at 16:12
  • can you refer me to an article or tutorial about implementation of tokens. – Yousuf Kalim Nov 13 '20 at 03:30
0

Your function checkAuth since it makes a network call may not return the correct value in time for rendering, what you SHOULD do is create a state for whether or not the user is authenticated and have checkAuth update that state.

const [authenticated, updateAuthenticated] = useState(false);

... update your checkAuth to update the authenticated state

//Function to check authentication from server
const checkAuth = () => {
    axios.get('http://localhost:8000/notes', { withCredentials : true })
    .then(res => updateAuthenticated(res.data)); // double check to ensure res.data contains the actual value returned
  }

... update your rendering to use the state

<Route exact path='/notes' render={() => {
     return (authenticated ? 
         (<Notes />)
        : 
         (<Redirect to="/login" />)
       )
  }} />

... with this approach you can also set the default in useState to true or false and determine whether or not it's your server side code that's giving trouble

... dont for get to call checkAuth in a useEffect somewhere at the top

useEffect(()=>{
    checkAuth()
},[])
steff_bdh
  • 969
  • 2
  • 12
  • 30
  • this is working but only first time, when I refresh the page then automatically state's value changes to false and it redirects to login – Yousuf Kalim Nov 11 '20 at 15:53
  • then maybe you need to check your server m8, it's probably not keeping the user logged in; also in your login page you need to check if the user is authenticated (similar process) and if so then redirect to the home page, it all depends on how you've structured you application, think about what loads when and you'll get through it, but that's beyond the scope of this question, can't write the entire app for you m8 – steff_bdh Nov 12 '20 at 14:55
0

Consider showing a loader until the api call returns a value. Once the api call has returned a value then render the required component using a higher order component.

App.jsx

class App extends Component {

state = {
    login: false,
    loading: false,
}

componentDidMount() {
    //Function to check authentication from server
    this.setState( {loadnig:true}, () => {
    this.checkAuth()
    }
}

checkAuth = () => {
    // API request to check if user is Authenticated
    axios.get('http://localhost:8000/notes', { withCredentials : true })
    .then( res => {
        console.log(res.data);
        // API response with data => login success!
        this.setState({
            login: true,
            loading:false
        });
    });
    .catch( error => {
        console.error(error);
        // Handle error
        this.setState({
            loading:false
        });
    });
}


render() {
    let {login, loading} = this.state
    let protectedRoutes = null;
    
    if(loading){
      return <loader/>
    }

    return (
        <Switch>
            //use authorize HOC with three parameters: component, requiresLogin, isLoggedIn
            <Route exact path='/path' component={authorize(component, true, login)} />
            <Route exact path='/' component={Register} />
            <Route exact path='/login' component={Login} />
        </Switch>
    );
}
};

export default App;

Using a Higher Order Component will give you the flexibility to control routes based on the login status of the users.

Authorize,jsx

export default function (componentToRender, requiresLogin, isLoggedIn) {

  class Authenticate extends React.Component<> {
  render() {
      if (requiresLogin) {
          //must be logged in to view the page
          if (!isLoggedIn) {
              // redirect to an unauthorised page or homepage
              this.props.history.push('/unauthorised');
              return;
              // or just return <unauthoriesd {...this.props} />;
          }
      } else {
          // to be shown to non loggedin users only. Like the login or signup page
          if (isLoggedIn) {
              this.props.history.push('/unauthorised');
              return;
              //or just return <home {...this.props} />;
          }
      }
      //if all the conditions are satisfied then render the intended component.
      return <componentToRender {...this.props} />;
  }

  }
  return Authenticate;
}

Also, if you ever decide to add in more conditions for the routing then you can easily add those to the HOC.

Prakash
  • 16
  • 1
  • Ok I'm considering to implement this, But before that do you think I should use JWT authorisation instead of this? Because I'm not sure the way I'm doing is a standard. – Yousuf Kalim Nov 13 '20 at 04:42
  • I mean I want to know if you have to do authentication for your project then what method you'll use? – Yousuf Kalim Nov 13 '20 at 04:43
  • @YousufKalim, JWT is a good way to start out. It is widely used for user auth. You can refer [this](https://blog.logrocket.com/mern-app-jwt-authentication-part-1/) blog post for more info on JWT. This will help you persist the user auth state after he has logged in once. – Prakash Nov 13 '20 at 07:30