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

如何让基于Firebase Auth的React应用定期验证用户Token?

实现页面切换时自动验证用户权限的方案

核心思路

每次路由切换时触发用户Token验证请求,对比后端返回的权限与本地存储的权限,若不一致则更新用户状态,触发应用重渲染,无需手动硬刷新。

具体实现步骤与代码修改

1. 引入路由监听依赖

在UserProvider中引入React Router的useLocation,用于监听路由路径变化。

2. 优化验证逻辑

  • 新增状态防止重复验证请求
  • 创建新用户对象更新状态(避免直接修改原对象导致React无法检测变化)
  • 对比权限是否变更,仅在变化时更新状态,减少不必要重渲染

3. 整合路由监听与验证

添加useEffect监听路由路径变化,触发用户验证。

修改后的完整代码

import { createContext, useContext, useState, useEffect } from "react";
import { useLocation } from "react-router-dom"; // 新增:路由监听工具
import { auth, provider } from "providers/firebase";
import {
  getAuth,
  onAuthStateChanged,
  signInWithPopup,
  signOut as firebaseSignOut
} from "firebase/auth";
import { api } from "providers/axios";
import { useLoading } from "providers/loading";

const UserContext = createContext(null);
export const useAuth = () => useContext(UserContext);

const verifyToken = (token) =>
  api({
    method: "post",
    url: "/user/auth",
    headers: {
      token
    }
  });

const UserProvider = (props) => {
  const [user, setUser] = useState(null);
  const [isVerifying, setIsVerifying] = useState(false); // 新增:防止重复验证请求
  const { loading, setLoading } = useLoading();
  const location = useLocation(); // 新增:获取当前路由信息

  const signIn = async () => {
    setLoading(true);
    try {
      const result = await signInWithPopup(auth, provider);
      console.log("auth signInWithPopup", result.user.email);
    } catch (e) {
      setUser(null);
      console.error(e);
      setLoading(false);
    }
  };

  const signOut = async () => {
    try {
      await firebaseSignOut(auth);
      setUser(null);
      console.log("signed out");
    } catch (e) {
      console.error(e);
    }
  };

  const verifyUser = async (currentUser) => {
    if (isVerifying) return; // 新增:正在验证时直接返回,避免重复请求
    setIsVerifying(true);
    
    try {
      if (!currentUser) throw "no user";

      // 直接使用传入的currentUser,避免依赖全局getAuth()
      const token = await currentUser.getIdToken(true);
      if (!token) throw "no token";

      const jwt = await currentUser.getIdTokenResult();
      if (!jwt) throw "no jwt";

      const verifyTokenResponse = await verifyToken(token);
      // 判断权限是否发生变化
      const permissionsChanged = user?.verifiedToken?.role !== verifyTokenResponse.data.role;

      // 创建新用户对象,确保React能检测到状态更新
      const updatedUser = {
        ...currentUser,
        verifiedToken: verifyTokenResponse.data
      };

      if (permissionsChanged || !user) {
        setUser(updatedUser);
        console.log(`User ${currentUser.uid} permissions updated`);
      } else {
        console.log(`User ${currentUser.uid} permissions unchanged`);
      }
    } catch (e) {
      signOut();
      console.error(e);
    } finally {
      setIsVerifying(false);
    }
  };

  // 原有Firebase Auth状态监听逻辑
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
      setLoading(true);
      try {
        if (currentUser) {
          console.log("onAuthStateChanged", currentUser?.email);
          await verifyUser(currentUser);
        } else {
          setUser(null);
          throw "no user";
        }
      } catch (e) {
        console.error(e);
      } finally {
        setLoading(false);
      }
    });
    return unsubscribe;
  }, []);

  // 新增:路由切换时触发验证
  useEffect(() => {
    const currentAuthUser = getAuth().currentUser;
    // 仅当用户已登录且本地有用户状态时执行验证
    if (currentAuthUser && user) {
      verifyUser(currentAuthUser);
    }
  }, [location.pathname]); // 依赖路由路径变化

  return (
    <UserContext.Provider
      value={{
        signIn,
        signOut,
        user,
        isVerifying // 可选:暴露验证状态供组件使用
      }}
    >
      {props.children}
    </UserContext.Provider>
  );
};
export default UserProvider;

关键修改说明

  • 路由监听:通过useLocation监听路径变化,每次页面切换自动触发验证
  • 重复请求防护:isVerifying状态避免路由快速切换时发送多个验证请求
  • 状态更新优化:创建新用户对象而非直接修改原对象,确保React状态更新生效
  • 权限变更判断:仅在权限实际变化时更新状态,减少不必要的组件重渲染
  • 代码健壮性:直接使用传入的currentUser,避免依赖全局getAuth().currentUser的潜在问题

额外优化建议

  • 防抖处理:如果路由切换频繁,可添加lodash.debounce对验证请求防抖,减少后端压力
  • 后端缓存:后端可对Token验证请求添加短时间缓存,降低数据库查询频率
  • 错误重试:验证请求失败时可添加有限次数的重试逻辑,提升用户体验
  • 加载提示:路由切换验证时,可在导航栏或页面显示加载状态,避免用户困惑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 05:01:12