How to keep authenticated state on refresh?

Viewed 894

I'm using firebase authentication for my app. I used useAuth hook from here. Integrate with react-router guide about redirect (Auth).

SignIn,SignOut function is working as expected. But when I try to refresh the page. It redirects to /login again.

My expected: Redirect to / route when authenticated.

I tried to add this code in PrivateRoute.js

  if (auth.loading) {
    return <div>authenticating...</div>;
  }

So I can refresh the page without redirect to /login but it only show authenticating... when click the log out button.

Here is my code: https://codesandbox.io/s/frosty-jennings-j1m1f?file=/src/PrivateRoute.js

What I missed? Thanks!

2 Answers

Issue

Seems you weren't rendering the "authenticating" loading state quite enough.

I think namely you weren't clearing the loading state correctly in the useEffect in useAuth when the initial auth check was resolving.

Solution

Set loading true whenever initiating an auth check or action, and clear when the check or action completes.

useAuth

function useProvideAuth() {
  const [loading, setLoading] = useState(true); // <-- initial true for initial mount render
  const [user, setUser] = useState(null);

  // Wrap any Firebase methods we want to use making sure ...
  // ... to save the user to state.
  const signin = (email, password) => {
    setLoading(true); // <-- loading true when signing in
    return firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then((response) => {
        setUser(response.user);
        return response.user;
      })
      .finally(() => setLoading(false)); // <-- clear
  };

  const signout = () => {
    setLoading(true); // <-- loading true when signing out
    return firebase
      .auth()
      .signOut()
      .then(() => {
        setUser(false);
      })
      .finally(() => setLoading(false)); // <-- clear
  };

  // Subscribe to user on mount
  // Because this sets state in the callback it will cause any ...
  // ... component that utilizes this hook to re-render with the ...
  // ... latest auth object.
  useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
      setLoading(false); // <-- clear
    });

    // Cleanup subscription on unmount
    return () => unsubscribe();
  }, []);

  // Return the user object and auth methods
  return {
    loading,
    user,
    signin,
    signout
  };
}

Check the loading state in PrivateRoute as you were

function PrivateRoute({ children, ...rest }) {
  const auth = useAuth();

  if (auth.loading) return "authenticating";

  return (
    <Route
      {...rest}
      render={({ location }) =>
        auth.user ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

Demo

Edit how-to-keep-authenticated-state-on-refresh

Try this approach, it works for me :

const mapStateToProps = state => ({
  ...state
});

function ConnectedApp() {
     const [auth, profile] = useAuth()

     const [isLoggedIn, setIsLoggedIn] = useState(false)

     useEffect(() => {

       if (auth && auth.uid) {
          setIsLoggedIn(true)
       } else {
          setIsLoggedIn(false)
       }
     }, [auth, profile]);

     return (<Router>
       <Redirect to="/app/home"/>
       <div className="App">
       <Switch>

        <Route path="/home"><Home/></Route>
        <Route path="/login"><Login styles={currentStyles}/></Route>
        <Route path="/logout"><Logout styles={currentStyles}/></Route>
        <Route path="/signup" render={isLoggedIn
            ? () => <Redirect to="/app/home"/>
            : () => <Signup styles={currentStyles}/>}/>
        <Route path="/profile" render={isLoggedIn
            ? () => <Profile styles={currentStyles}/>
            : () => <Redirect to="/login"/>}/>

      </Switch>
    </div>
  </Router>);
    }
const App = connect(mapStateToProps)(ConnectedApp)

export default App;
Related