I am trying to setup the Passwordless Firebase Authentication in my React (hook-based) project.
The desired workflow looks like this:
- The user goes to the LoginPage (going to any PrivateRoute should not be allowed and should redirect the user to the LoginPage).
- The user enters their email and presses Send. There email is saved in local storage.
- The user receives an email with a link. They press the link and are sent back to LoginPage.
- LoginPage reads the saved email from LocalStorage and logs the user in.
- 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>
);