Password-less Authentication with Firebase and React

Viewed 560

I am trying to setup the Passwordless Firebase Authentication in my React (hook-based) project.

The desired workflow looks like this:

  1. The user goes to the LoginPage (going to any PrivateRoute should not be allowed and should redirect the user to the LoginPage).
  2. The user enters their email and presses Send. There email is saved in local storage.
  3. The user receives an email with a link. They press the link and are sent back to LoginPage.
  4. LoginPage reads the saved email from LocalStorage and logs the user in.
  5. The user can now go to the any PrivateRoute.

By following this guid on Firebase Email-Link Authentication and this guide on PrivateRoute In React I have managed to get steps 1-4 complete. The problem I have now is that the even after being authenticated by Firebase and setting isAuth = true, when I visit localhost:8080/dashboard (a PrivateRoute) the value of isAuth appears to still be false and I am redirected back to the LoginPage.

What am I missing? Thanks!

LoginPage.js

const LoginPage = () => {
    const [email, setEmail] = useState(window.localStorage.getItem("emailForSignIn") || "");
    const [message, setMessage] = useState("");
    const { onLogin } = useAuthDataContext();

    useEffect(() => {
        const savedEmail = window.localStorage.getItem("emailForSignIn");
        if (firebase.auth().isSignInWithEmailLink(window.location.href) && !!savedEmail) {
            firebase
                .auth()
                .signInWithEmailLink(savedEmail, window.location.href)
                .then(() => {
                    window.localStorage.removeItem("emailForSignIn");
                })
                .then(onLogin)
                .catch((error) => {
                    console.log(error);
                    setMessage(error);
                });
        }
    }, []);

    const onSubmit = () => {
        if (firebase.auth().isSignInWithEmailLink(window.location.href) && !!email) {
            firebase
                .auth()
                .signInWithEmailLink(email, window.location.href)
                .then(() => {
                    window.localStorage.removeItem("emailForSignIn");
                })
                .then(onLogin)
                .catch((error) => {
                    console.log(error);
                });
        } else {
            const isValidEmail = validateEmail(email);
            if (isValidEmail) {
                firebase
                    .auth()
                    .sendSignInLinkToEmail(email, {
                        url: "http://localhost:8080",
                        handleCodeInApp: true
                    })
                    .then(() => {
                        window.localStorage.setItem("emailForSignIn", email);
                        setMessage("Email sent");
                    })
                    .catch((error) => {
                        setMessage(error);
                        console.log(error);
                    });
            } else {
                setMessage("Email is not valid");
            }
        }
    };

    const onEmailChange = (e) => {
        setEmail(e.target.value);
    };

    return (
        <div>
            <p>Please enter your email and press send</p>
            <input type="text" value={email} onChange={onEmailChange} />
            <button type="button" onClick={() => onSubmit()}>
                Send
            </button>
            <p>{message}</p>
        </div>
    );
};

useAuthContext.js

export const AuthDataContext = createContext(null);

const AuthProvider = (props) => {
    const [isAuth, setIsAuth] = useState(false);

    useEffect(() => {
        firebase.auth().onAuthStateChanged((user) => {
            if (user) {
                setIsAuth(true);
            }
        });
    }, []);

    const onLogin = () => setIsAuth(true);

    const onLogout = () => setIsAuth(false);

    return <AuthDataContext.Provider value={{ isAuth, onLogin, onLogout }} {...props} />;
};

export default AuthProvider;
export const useAuthDataContext = () => useContext(AuthDataContext);

PrivateRoute.js

const PrivateRoute = ({ component: Component, ...rest }) => {
    const { isAuth } = useAuthDataContext();

    // isAuth is False even after onLogin is called
    console.log("isAuth", isAuth);
    return <Route {...rest} component={(props) => (isAuth ? <Component {...props} /> : <Redirect to="/" />)} />;
};

AppRouter.js

const AppRouter = () => (
    <BrowserRouter>
        <AuthProvider>
            <Switch>
                <Route path="/" component={LoginPage} exact />
                <PrivateRoute path="/dashboard" component={DashboardPage} />
            </Switch>
        </AuthProvider>
    </BrowserRouter>
);
0 Answers
Related