Search code examples
javascriptreactjsexpressauthenticationpassport.js

How do I fetch data from API before render method called? (react, passport, express)


I am trying to verify that the user is authenticated with passport before allowing them to access a certain route. To do so, I need to access my API which will return the authentication status of my user. However, I wish to make the call before the route has rendered so that the route remains protected. Here is my current attempt:

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

class ProtectedRoute extends Component {
    constructor(){
        super()
        this.fetchAuthState().then(result => console.log('result',result))
        this.state = { 
            isAuth: false, 
            error: null
        }
    }
    async fetchAuthState() {
        try {
            const response = await fetch('http://localhost:3001/logincheck', {
              headers: {
                'content-type': 'application/json',
                accept: 'application/json',
              },
            });
            return await response.json();
          } catch (error) {
            console.error(error);
            this.setState({ error });
          }
      };

    render() {
      console.log('client check', this.state.isAuth)
      const { component: Component, ...props } = this.props
      return (
        <Route 
          {...props} 
          render={props => (
            this.state.isAuth ?
              <Component {...props} /> :
              <Redirect to='/login' />
          )} 
        />
      )
    }
  }

  export default ProtectedRoute; 

Here is the server side code:

app.get('/logincheck', (req, res) =>{
  res.send(req.isAuthenticated())
})

I figured that because of the react lifecycle, the fetch should be called in the constructor, and then stored in state. However, when I try to store the result, either directly or in a temporary variable, the fetch result shows undefined. I have checked the passport docs for any client side form of isAuthenticated(), but it appears that it only works server side. I was considering implementing a jwt token system to see if that would be a better way to maintain authentication status but thought that having an api route to check would be easier. I am new to web development so any advice/criticism would be much appreciated!


Solution

  • You can accomplish this by adding an extra state variable - something like loading - and then toggling it when the API returns a response. But I would refactor your code slightly to make it look like so:

    import React, { Component } from 'react';
    import { Route, Redirect } from 'react-router-dom';
    
    class ProtectedRoute extends Component {
        constructor(){
            super()
            this.state = {
                loading: true,
                isAuth: false, 
                error: null
            }
        }
        componentDidMount(){
            fetch('http://localhost:3001/logincheck', {
                  headers: {
                    'content-type': 'application/json',
                    accept: 'application/json',
                  },
                })
                .then(res => res.json())
                .then(json => {
                  this.setState({isAuth: json.isAuth, loading: false})
                })
                .catch(e => console.log(e))
          }
    
        render() {
          console.log('client check', this.state.isAuth)
          const { component: Component, ...props } = this.props;
          const {loading, isAuth} = this.state;
          return (
            <Route 
              {...props} 
              render={() => {
                return loading ?
                    <div>Loading...</div>
                    :
                    isAuth ?
                       this.props.children
                    :
                        <Redirect to='/login' />
            }} />
          )
        }
      }
    
      export default ProtectedRoute;