You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase认证中onAuthStateChanged每次返回null问题排查

问题根因及修复方案
  • 最核心的问题:前端Firebase SDK没有同步后端返回的认证状态
    你当前的注册/登录逻辑是把账号密码传给自定义后端,由后端调用Firebase Admin SDK创建用户/生成令牌,这个流程下前端Firebase SDK本身没有参与登录流程,不会自动存储用户认证凭证,所以onAuthStateChanged自然监不到已登录的用户,返回null。
    你需要拿到后端返回的自定义令牌后,主动调用Firebase客户端的signInWithCustomToken方法同步状态。

  • 次要问题:onAuthStateChanged监听时机错误、重复注册
    你是在接口返回数据后才注册onAuthStateChanged监听器,而该监听器的首次触发是在Firebase SDK初始化完成时,晚注册会错过初始状态同步;同时每次data变化都会重复注册监听器,没有清理逻辑会导致内存泄漏。


修复后的代码示例

import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap-icons/font/bootstrap-icons.css';
import { useEffect, useRef, useState } from 'react';
import useFetch from '../hooks/useFetch';
import { useLocation } from 'react-router';
import { auth } from '../config/firebaseConfig';
// 新增导入Firebase认证相关方法
import { onAuthStateChanged, signInWithCustomToken, Unsubscribe } from 'firebase/auth';

export default function Authenticate() : JSX.Element {
    const url = useLocation().pathname;
    const { fetchData, data, error, loading } = useFetch();

    const email = useRef() as React.MutableRefObject<HTMLInputElement>;
    const password = useRef() as React.MutableRefObject<HTMLInputElement>;
    const [show, setShow] = useState(false);
    // 存储监听器销毁函数
    const authUnsubscribeRef = useRef<Unsubscribe | null>(null);

    const handleAuth = (e : React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();

        const path = (e.nativeEvent as any).submitter.name === 'login' ?
        `${url}/login/email` : `${url}/register`;
        fetchData(path, 'POST', {
            email : email.current.value,
            password : password.current.value
        });
    }

    // 组件挂载时就注册auth状态监听
    useEffect(() => {
        authUnsubscribeRef.current = onAuthStateChanged(auth, user => {
            console.log('当前登录用户:', user);
            // 可在此处添加登录成功后的跳转逻辑
        });
        // 组件卸载时清理监听器
        return () => {
            authUnsubscribeRef.current?.();
        }
    }, [])

    // 处理后端返回的认证令牌
    useEffect(() => {
        if(data?.token) { // 请根据你后端实际返回的token字段调整属性名
            // 主动同步登录状态到前端Firebase SDK
            signInWithCustomToken(auth, data.token)
                .catch(err => console.error('Firebase客户端登录失败:', err));
        }
    }, [data])
    
    return (
        <div className="d-flex justify-content-center align-items-center vh-100 bg-white">
            <div className="p-5 border border-2 border-success"> 
                <h1 className="fw-light fs-2 text-center">Authenticate</h1>
                <br/>
                <form onSubmit={e => handleAuth(e)}>
                    <div className="form-group">
                        <label>Email</label>
                        <div className="input-group">
                            <span className="input-group-text bi bi-person-circle"></span>
                            <input type="email" ref={email} className="form-control" placeholder="boomer@bommer.com" required/>
                        </div>
                    </div>
                    <div className="form-group">
                        <label>Password</label>
                        <div className="input-group">
                            <span className="input-group-text bi bi-key-fill"/>
                            <input type={show ? "text" : "password"} className="form-control" ref={password} placeholder="enter your password" required/>
                            <button type="button" className={`input-group-text text-decoration-none bi bi-${show ? 'eye-fill' : 'eye-slash-fill' }`} onClick={() => setShow(!show)}/>
                        </div>
                    </div>
                    <br/>
                    { error && <p className={`alert alert-danger p-1 text-center`} role="alert">{error}</p> }
                    <div className="d-flex justify-content-between">
                        <button type="submit" name="register" className="btn btn-primary" disabled={loading}>Register</button>
                        <button type="submit" name="login" className="btn btn-primary" disabled={loading}>Login</button>
                    </div>
                </form>
            </div>
        </div>
  );
}

内容的提问来源于stack exchange,提问作者shalev sror

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 21:36:02