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

Express+React+TypeScript下JWT鉴权优化及Wrapper异步问题咨询

React+Express+TypeScript鉴权路由问题解决

问题背景

使用Express+React+TypeStack开发时,希望通过每次请求携带token到后端验证用户登录状态。目前每个私有路由都需要编写重复的鉴权逻辑,即便复用了鉴权检查函数,仍要写页面条件渲染代码。尝试用Wrapper组件统一处理,但因React状态更新的异步特性,isAuth初始值为false,导致每次都会跳转到首页。

后端鉴权接口代码

export const isAuth = async (req: Request, res: Response) => {
  const token = req.get('Authorization')?.split(' ')[1];
  if (token) {
    try {
      const decodedToken = jwt.verify(token, `${process.env.SECRET_KEY}`);
      if (decodedToken) res.status(200).json({ isAuth: true });
    } catch (e) {
      res.status(403).json({ isAuth: false });
    }
  } else res.status(403).json({ isAuth: false });
};

原有前端私有路由代码

const AddProductPage = () => {
  const [isAuth, setIsAuth] = useState<boolean>(false);
  useEffect(() => {
    const checkAuth = async () => {
      const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + localStorage.getItem('token'),
        },
      });
      const data = await response.json();
      setIsAuth(data.isAuth);
    };

    checkAuth();
  }, []);
  return isAuth ? <AddProduct /> : <Navigate to="/" />;
};

尝试的Wrapper组件代码

interface WrapperProps {
  children: React.ReactNode;
}
const IsAuth: React.FC<WrapperProps> = (props) => {
  const [isAuth, setIsAuth] = useState(false);
  useEffect(() => {
    const checkAuth = async () => {
      const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + localStorage.getItem('token'),
        },
      });
      const data = await response.json();
      setIsAuth(data.isAuth);
    };
    checkAuth();
  }, []);
  console.log('ISAUTH', isAuth);
  return isAuth ? <div>{props.children}</div> : <Navigate to="/" />;
};

解决方案

1. 增加加载状态避免初始误跳转

React状态更新是异步的,组件挂载时isAuth初始值为false会直接触发跳转。需要添加加载状态,在鉴权请求完成前不执行跳转或显示加载组件:

interface WrapperProps {
  children: React.ReactNode;
}
const IsAuth: React.FC<WrapperProps> = (props) => {
  // 初始设为null表示鉴权未完成
  const [isAuth, setIsAuth] = useState<boolean | null>(null); 

  useEffect(() => {
    const checkAuth = async () => {
      const token = localStorage.getItem('token');
      // 本地无token直接标记为未授权
      if (!token) {
        setIsAuth(false);
        return;
      }
      try {
        const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
          method: 'PATCH',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${token}`,
          },
        });
        const data = await response.json();
        setIsAuth(data.isAuth);
      } catch (err) {
        // 请求失败也标记为未授权
        setIsAuth(false);
      }
    };

    checkAuth();
  }, []);

  // 鉴权未完成时显示加载组件
  if (isAuth === null) {
    return <div>加载中...</div>; // 可替换为自定义Loading组件
  }

  return isAuth ? <>{props.children}</> : <Navigate to="/" />;
};

2. 优化后端鉴权逻辑

简化响应逻辑,同时区分不同错误场景:

export const isAuth = async (req: Request, res: Response) => {
  try {
    const authHeader = req.get('Authorization');
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({ isAuth: false, message: '缺少有效token' });
    }
    const token = authHeader.split(' ')[1];
    const decodedToken = jwt.verify(token, `${process.env.SECRET_KEY}`);
    // 返回解码后的用户信息,方便前端复用
    res.status(200).json({ isAuth: true, user: decodedToken });
  } catch (e) {
    if (e instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ isAuth: false, message: 'token已过期' });
    }
    res.status(403).json({ isAuth: false, message: '无效token' });
  }
};

3. 路由层面统一使用Wrapper

在路由配置中直接用IsAuth包裹所有私有路由,无需每个页面重复编写逻辑:

// 路由配置示例
<Routes>
  <Route path="/" element={<HomePage />} />
  <Route path="/add-product" element={
    <IsAuth>
      <AddProduct />
    </IsAuth>
  } />
  {/* 其他私有路由同理 */}
</Routes>

4. 可选:封装鉴权请求函数

将鉴权请求封装为独立工具函数,便于全局复用:

// src/utils/auth.ts
export const checkAuthStatus = async (): Promise<boolean> => {
  const token = localStorage.getItem('token');
  if (!token) return false;
  try {
    const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`,
      },
    });
    const data = await response.json();
    return data.isAuth;
  } catch (err) {
    return false;
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 04:36:22