Home > Software engineering >  why auth.currentUser is null on page refresh in Firebase Authetication
why auth.currentUser is null on page refresh in Firebase Authetication

Time:08-20

I have implemented Firebase Auth (Sign In With Google) in a MERN stack app.

The /admin client-side route is a protected route. After I log in, I see the displayName of the logged in user, as shown below:

enter image description here

Admin.js

import React, { useContext } from "react";
import { Link } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { getAuth, signOut } from "firebase/auth";
import { useFetchAllPosts } from "../hooks/postsHooks";
import Spinner from "../sharedUi/Spinner";
import { PencilAltIcon } from "@heroicons/react/outline";
import { UserContext } from "../context/UserProvider";

const Admin = () => {
  const { data: posts, error, isLoading, isError } = useFetchAllPosts();

  const auth = getAuth();
  const navigate = useNavigate();

  const { name } = useContext(UserContext);

  const handleLogout = () => {
    signOut(auth).then(() => {
      navigate("/");
    });
  };

  return (
    <>
      <h2>Hello {name}</h2>
      <button className="border" onClick={handleLogout}>
        Logout
      </button>
      <div>
        {isLoading ? (
          <Spinner />
        ) : isError ? (
          <p>{error.message}</p>
        ) : (
          posts.map((post) => (
            <div
              key={post._id}
              className="flex w-80 justify-between px-6 py-2 border rounded mb-4 m-auto"
            >
              <Link to={`/posts/${post._id}`}>
                <h2>{post.title}</h2>
              </Link>
              <Link to={`/posts/edit/${post._id}`}>
                <PencilAltIcon className="w-5 h-5" />
              </Link>
            </div>
          ))
        )}
      </div>
    </>
  );
};

export default Admin;

App.js

import React from "react";
import { Routes, Route } from "react-router-dom";
import Home from "./components/screens/Home";
import PostCreate from "./components/screens/PostCreate";
import SinglePost from "./components/screens/SinglePost";
import Admin from "./components/screens/Admin";
import PostEdit from "./components/screens/PostEdit";
import Login from "./components/screens/Login";
import PrivateRoute from "./components/screens/PrivateRoute";

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/admin" element={<PrivateRoute />}>
        <Route path="/admin" element={<Admin />} />
      </Route>
      <Route path="/posts/create" element={<PostCreate />} />
      <Route path="/posts/:id" element={<SinglePost />} />
      <Route path="/posts/edit/:id" element={<PostEdit />} />
      <Route path="/login" element={<Login />} />
    </Routes>
  );
};

export default App;

PrivateRoute.js

import React from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { getAuth } from "firebase/auth";

const PrivateRoute = () => {
  const location = useLocation();

  const auth = getAuth();

  console.log(auth.currentUser);

  return auth.currentUser ? (
    <Outlet />
  ) : (
    <Navigate to="/login" state={{ from: location }} />
  );
};

export default PrivateRoute;

Now, if I refresh this page, I go back to the /login route.

enter image description here

However, this should not happen, because if I go to the root / route, I see the displayName of the current user.

enter image description here

If I refresh the page while on the root / route, I still see the displayName of the current user.

So, my question is why am I getting redirected to the /login page after I refresh the page on the /admin route? I am logged in, so I should remain on the Admin page.

The logic of whether a user is logged-in or not is implemented in the UserProvider component:

UserProvider.js

import React, { useState, useEffect, createContext } from "react";
import { getAuth, onAuthStateChanged } from "firebase/auth";

export const UserContext = createContext();

const UserProvider = ({ children }) => {
  const [name, setName] = useState(null);

  const auth = getAuth();

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      if (user) {
        console.log(user);
        setName(user.displayName);
      } else {
        setName(null);
      }
    });

    return unsubscribe;
  }, [auth]);

  const user = {
    name,
    setName,
  };

  return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};

export default UserProvider;

CodePudding user response:

The Firebase Authentication SDK automatically restores the user upon reloading the page/app. This does require a call to the server - a.o. to check if the account hasn't been disabled. While that verification is happening, auth.currentUser will be null.

Once the user account has been restored, the auth.currentUser will get a value, and (regardless of whether the restore succeeded or failed) any onAuthStateChanged listeners are called.

What this means is that you should not check the auth.currentUser value in code that runs immediately on page load, but should instead react to auth state changes with a listener.

If you need to route the user based on their authentication state, that should happen in response to the auth state change listener too. If you want to improve on the temporary delay that you get from this, you can consider implementing this trick that Firebaser Michael Bleigh talked about at I/O a couple of years ago: https://www.youtube.com/watch?v=NwY6jkohseg&t=1311s

CodePudding user response:

I figured out a solution. I installed the react-firebase-hooks npm module and used the useAuthState hook to monitor the authentication status.

PrivateRoute.js

import React from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { getAuth } from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";

const PrivateRoute = () => {

  const location = useLocation();

  const auth = getAuth();
  const [user, loading] = useAuthState(auth);

  if (loading) {
    return "Loading...";
  }

  return user ? (
    <Outlet />
  ) : (
    <Navigate to="/login" state={{ from: location }} />
  );
};

export default PrivateRoute;
  • Related