使用Firebase Authentication时刷新页面用户自动登出的原因排查
问题:刷新页面用户自动登出,setPersistence无效
我为个人项目开发了一套简单的登录注册功能,首次开发这类功能时遇到刷新页面用户就会登出的问题。怀疑是认证创建方式有误,但无法定位原因,尝试过使用setPersistence但没有效果。
登录页面代码
import React, { useState, useEffect } from 'react' import { Link, useNavigate } from 'react-router-dom' import { signInWithEmailAndPassword } from 'firebase/auth' import { auth } from '/src/firebase.js' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faX } from '@fortawesome/free-solid-svg-icons' import { setPersistence, browserSessionPersistence, onAuthStateChanged } from 'firebase/auth' export default function Login({ userLogin, setUserLogin }) { useEffect(() => { if (typeof grecaptcha !== 'undefined') { grecaptcha.ready(() => { grecaptcha.render('Login-reCaptcha') }) } },[]) const [submitNotification,setSubmitNotification] = useState({ show: false, msg: "" }) // notification for any responses from requests e.g form submition function handleInput(e) { setUserLogin(prevInput => {return { ...prevInput, [e.target.name]: e.target.value }}) } const navigate = useNavigate() const handleLogin = async (e) => { e.preventDefault(e) if (grecaptcha.getResponse() == "") { setSubmitNotification(prevValue => {return{ show: true, msg: "Please complete the reCaptcha!" }}) } else { try { const user = await signInWithEmailAndPassword( auth, userLogin.email, userLogin.password ) if (user) { handleUserAuth() navigate("/Getting-Started") } } catch(error) { console.log(error) if (error == "FirebaseError: Firebase: Error (auth/user-not-found).") { setSubmitNotification(prevValue => {return { show: true, msg: "User does not exist." }}) } else if (error == "FirebaseError: Firebase: Error (auth/wrong-password).") { setSubmitNotification(prevValue => {return{ show: true, msg: "Your email/password is incorrect" }}) } else if (error == "FirebaseError: Firebase: Access to this account has been temporarily disabled due to many failed login attempts. You can immediately restore it by resetting your password or you can try again later. (auth/too-many-requests).") { setSubmitNotification(prevValue => {return { show: true, msg: "User blocked, please try again later" }}) } } } } const handleUserAuth = async (e) => { setPersistence(auth, browserSessionPersistence) .then(() => { console.log("persistence ran") return signInWithEmailAndPassword(auth, userLogin.email, userLogin.password) }) .catch((error) => { console.log(error.code) console.log(error.messsage) }) } return ( <div> <div className='flex justify-start items-center w-full h-24 p-4 absolute top-5 left-0'> <img className='h-28 hidden sm:inline-block' src='/ebayanalyticscut.png'></img> </div> <div className='h-screen flex justify-center items-center text-white font-poppins'> <div className='sm:bg-secondary h-[550px] w-[530px] rounded-2xl overflow-hidden p-5'> <form className='flex flex-col items-center justify-center' onSubmit={handleLogin}> <h1 className='text-3xl mb-5 font-medium'>Welcome Back!</h1> {submitNotification.show && <div className='bg-red w-[350px] h-10 rounded-md mb-4 flex flex-row items-center p-3'> <FontAwesomeIcon icon={faX} className='h-3 mr-4 mt-0.5 cursor-pointer' onClick={() => setSubmitNotification(prevValue => {return{...prevValue, show: false}})}/> <p className='text-lg'>{submitNotification.msg}</p> </div>} <div className='mb-6'> <label className='text-lg block'>Email Address</label> <input value={userLogin.email} autoComplete='off' name='email' type='email' className='h-10 w-[350px] rounded-xl text-black pl-2 font-medium' onChange={handleInput}></input> </div> <div className='mb-9'> <label className='text-lg block'>Password</label> <input value={userLogin.password} autoComplete='off' name='password' type='password' className='h-10 w-[350px] rounded-xl text-black pl-2 font-medium' onChange={handleInput}></input> </div> <div id='Login-reCaptcha' className="g-recaptcha mb-6 -mt-3" data-sitekey={import.meta.env.VITE_RECAPTCHA_SITE_KEY} data-theme="dark"></div> <button className='bg-accent w-[300px] h-12 rounded-2xl text-lg mb-5'>Log In</button> <p>Don't have an account? <Link to='/' className='underline text-accent'>Sign Up</Link></p> </form> </div> </div> </div> ) }
App.jsx代码
import React, { useState } from 'react' import './index.css' import { Routes, Route } from 'react-router-dom' import Signup from './pages/Signup' import Login from './pages/Login' import Start from './pages/Start' import { setPersistence, browserSessionPersistence, signInWithEmailAndPassword, onAuthStateChanged } from 'firebase/auth' import { auth } from '/src/firebase.js' export default function App() { const [userSignup, setUserSignup] = useState({ email: "", password: "", confirmpassword: "" }) const [userLogin,setUserLogin] = useState({ email: "", password: "" }) return ( <div> <Routes> <Route path='/' element={<Signup userSignup={userSignup} setUserSignup={setUserSignup}/>}/> <Route path='/Login' element={<Login userLogin={userLogin} setUserLogin={setUserLogin}/>}/> <Route path='/Getting-Started' element={<Start userLogin={userLogin}/>} /> </Routes> </div> ) }
问题分析与修复方案
1. 重复登录导致持久化设置失效
你在handleLogin中已经调用了signInWithEmailAndPassword完成登录,随后又调用handleUserAuth再次执行登录操作,这会覆盖之前的登录状态,且setPersistence必须在登录前设置才能生效。
修改handleLogin,将持久化设置和登录合并为一次操作:
const handleLogin = async (e) => { e.preventDefault(e) if (grecaptcha.getResponse() == "") { setSubmitNotification(prevValue => ({ show: true, msg: "Please complete the reCaptcha!" })) } else { try { // 先设置持久化,再执行登录 await setPersistence(auth, browserSessionPersistence) const user = await signInWithEmailAndPassword( auth, userLogin.email, userLogin.password ) if (user) { navigate("/Getting-Started") } } catch(error) { console.log(error) // 优化错误判断逻辑,直接用error.code更可靠 if (error.code === "auth/user-not-found") { setSubmitNotification({ show: true, msg: "User does not exist." }) } else if (error.code === "auth/wrong-password") { setSubmitNotification({ show: true, msg: "Your email/password is incorrect" }) } else if (error.code === "auth/too-many-requests") { setSubmitNotification({ show: true, msg: "User blocked, please try again later" }) } } } }
同时删除多余的handleUserAuth函数。
2. 未监听用户认证状态变化
当前App组件依赖userLogin这个表单状态来传递用户信息,但页面刷新后useState的初始值会重置为空对象,导致页面认为用户未登录。需要通过Firebase的onAuthStateChanged实时监听用户认证状态,同步到全局状态。
修改App.jsx:
import React, { useState, useEffect } from 'react' import './index.css' import { Routes, Route, Navigate } from 'react-router-dom' import Signup from './pages/Signup' import Login from './pages/Login' import Start from './pages/Start' import { onAuthStateChanged } from 'firebase/auth' import { auth } from '/src/firebase.js' export default function App() { const [userSignup, setUserSignup] = useState({ email: "", password: "", confirmpassword: "" }) const [userLogin,setUserLogin] = useState({ email: "", password: "" }) // 添加全局用户状态,存储Firebase返回的用户对象 const [currentUser, setCurrentUser] = useState(null) useEffect(() => { // 监听用户认证状态变化 const unsubscribe = onAuthStateChanged(auth, (user) => { setCurrentUser(user) // 如果用户存在,同步邮箱到userLogin状态(可选) if (user) { setUserLogin(prev => ({ ...prev, email: user.email })) } }) // 组件卸载时取消监听 return unsubscribe }, []) // 可选:添加路由保护,未登录时跳转到登录页 const ProtectedRoute = ({ children }) => { if (!currentUser) { return <Navigate to="/Login" /> } return children } return ( <div> <Routes> <Route path='/' element={<Signup userSignup={userSignup} setUserSignup={setUserSignup}/>}/> <Route path='/Login' element={<Login userLogin={userLogin} setUserLogin={setUserLogin}/>}/> <Route path='/Getting-Started' element={ <ProtectedRoute> <Start user={currentUser} /> </ProtectedRoute> } /> </Routes> </div> ) }
同时修改Start组件,接收user props而不是userLogin,因为currentUser包含完整的认证用户信息。
3. 注意持久化类型
browserSessionPersistence表示会话级持久化,关闭浏览器标签页后登录状态会消失。如果需要持久化到浏览器缓存(关闭浏览器后仍保留),可以改用browserLocalPersistence。
内容的提问来源于stack exchange,提问作者Harman
相关产品推荐
相关产品推荐

