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

React+Node.js集成外部API OAuth2登录:JWT存LocalStorage问题

解决方案:Node.js后端OAuth2集成JWT并将用户数据存入React前端LocalStorage

核心思路调整

原来的流程依赖后端数据库存储session,现在改成无状态JWT机制:后端在OAuth授权成功后生成JWT,通过重定向传递给前端,前端将JWT和用户信息存入LocalStorage,后续请求通过请求头携带JWT做身份验证;外部API的refresh_token存在后端缓存(如Redis)而非前端,避免泄露风险。

分步实现

1. 后端改造:移除Session,集成JWT与缓存

  • 安装依赖:npm install jsonwebtoken redis(用Redis存储外部API的tokens,替代数据库session)
  • 配置JWT密钥:将密钥存入环境变量(如JWT_SECRET),避免硬编码
  • 修改Passport OAuth回调逻辑:
    const jwt = require('jsonwebtoken');
    const redisClient = require('./redis-client'); // 初始化Redis连接
    
    // OAuth回调端点
    router.get('/auth/external/callback', passport.authenticate('external-oauth', { session: false }), (req, res) => {
      // 从req.user获取外部API返回的tokens和用户基础信息
      const { userId, username, accessToken, refreshToken } = req.user;
    
      // 生成JWT,包含用户标识,设置合理过期时间(如1小时)
      const authToken = jwt.sign(
        { userId, username },
        process.env.JWT_SECRET,
        { expiresIn: '1h' }
      );
    
      // 将外部API的tokens存入Redis,关联用户ID并设置过期时间
      redisClient.setEx(`user:${userId}:external-access`, 3600, accessToken);
      redisClient.setEx(`user:${userId}:external-refresh`, 86400 * 7, refreshToken);
    
      // 重定向到React前端的登录成功页面,用哈希参数携带JWT(避免服务器日志记录敏感信息)
      res.redirect(`http://your-react-domain.com/login-success#token=${authToken}`);
    });
    
  • 编写JWT验证中间件,用于保护后续接口:
    // auth-middleware.js
    const jwt = require('jsonwebtoken');
    
    module.exports = (req, res, next) => {
      const authHeader = req.headers.authorization;
      const token = authHeader?.split(' ')[1];
    
      if (!token) return res.sendStatus(401);
    
      jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
        if (err) return res.sendStatus(403); // token无效或过期
        req.user = decoded; // 将用户信息挂载到req对象
        next();
      });
    };
    

2. 前端处理:接收JWT并存入LocalStorage

在React的登录成功页面组件中,解析URL中的JWT并存储:

// src/pages/LoginSuccess.jsx
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import jwtDecode from 'jwt-decode'; // 需安装:npm install jwt-decode

const LoginSuccess = () => {
  const navigate = useNavigate();

  useEffect(() => {
    // 从URL哈希参数中获取JWT
    const hashParams = new URLSearchParams(window.location.hash.slice(1));
    const authToken = hashParams.get('token');

    if (authToken) {
      try {
        // 解析JWT获取用户信息
        const userInfo = jwtDecode(authToken);
        // 存入LocalStorage
        localStorage.setItem('authToken', authToken);
        localStorage.setItem('user', JSON.stringify(userInfo));

        // 跳转到应用主页
        navigate('/');
      } catch (err) {
        // 解析失败,跳回登录页
        navigate('/login');
      }
    } else {
      navigate('/login');
    }
  }, [navigate]);

  return <div>登录成功,正在跳转...</div>;
};

export default LoginSuccess;

3. 实现Token刷新机制

当前端的JWT过期时,调用后端刷新接口获取新的JWT:

  • 后端刷新接口示例:
    router.post('/auth/refresh', async (req, res) => {
      const { userId } = req.body; // 前端传入用户ID
      // 从Redis获取外部API的refreshToken
      const externalRefreshToken = await redisClient.get(`user:${userId}:external-refresh`);
    
      if (!externalRefreshToken) return res.sendStatus(401);
    
      // 调用外部API刷新accessToken
      const newExternalTokens = await fetch('https://external-api.com/refresh', {
        method: 'POST',
        body: JSON.stringify({ refresh_token: externalRefreshToken }),
        headers: { 'Content-Type': 'application/json' }
      }).then(res => res.json());
    
      // 更新Redis中的accessToken
      await redisClient.setEx(`user:${userId}:external-access`, 3600, newExternalTokens.access_token);
    
      // 生成新的JWT返回给前端
      const newAuthToken = jwt.sign(
        { userId, username: req.user.username },
        process.env.JWT_SECRET,
        { expiresIn: '1h' }
      );
    
      res.json({ token: newAuthToken });
    });
    
  • 前端可通过Axios拦截器,在请求前检查JWT是否过期,过期则调用刷新接口更新LocalStorage中的token。

安全注意事项

  • LocalStorage风险:JWT存在LocalStorage易受XSS攻击,需确保前端代码无XSS漏洞,或考虑用HttpOnly Cookie存储JWT(若业务允许)
  • 外部Token保护:绝不将外部API的refresh_token暴露给前端,始终存在后端缓存
  • JWT过期策略:设置较短的JWT过期时间(如1小时),配合刷新机制平衡安全与用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 07:45:29