如何在React.js与Express.js API中持久化AccessToken?
React + Express 实现安全的登录会话持久化方案
首选方案:HttpOnly Cookie + 服务器端Session
这是比LocalStorage更安全的方案,HttpOnly Cookie无法被前端JS读取,从根源避免XSS窃取会话的风险,配合CSRF防护就能解决你的问题。
后端(Express)配置步骤
- 安装依赖:
npm install express-session cookie-parser
- 配置session和cookie:
const express = require('express'); const session = require('express-session'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser()); app.use(session({ secret: '你的高强度随机密钥', // 建议用环境变量存储,不要硬编码 resave: false, saveUninitialized: false, cookie: { httpOnly: true, // 核心设置:禁止前端JS访问Cookie secure: process.env.NODE_ENV === 'production', // 生产环境必须开启HTTPS才生效 sameSite: 'strict', // 防范CSRF的关键设置之一 maxAge: 24 * 60 * 60 * 1000 // 会话有效期设为1天 } })); // 登录接口示例 app.post('/api/login', (req, res) => { // 此处替换为实际的账号密码验证逻辑 const user = { id: 1, username: 'test_user' }; req.session.user = user; // 将用户信息存入服务器Session res.status(200).json({ message: '登录成功' }); }); // 验证会话状态的接口示例 app.get('/api/profile', (req, res) => { if (req.session.user) { res.json({ user: req.session.user }); } else { res.status(401).json({ message: '未登录' }); } }); // 登出接口 app.post('/api/logout', (req, res) => { req.session.destroy(err => { if (err) { return res.status(500).json({ message: '登出失败' }); } res.clearCookie('connect.sid'); // 清除对应的Session Cookie res.status(200).json({ message: '登出成功' }); }); });
前端(React)处理
浏览器会自动携带HttpOnly Cookie,前端无需手动存储会话信息,只需确保请求携带凭证:
- 登录请求示例:
const handleLogin = async () => { try { const response = await fetch('/api/login', { method: 'POST', credentials: 'include', // 关键:告诉浏览器携带Cookie headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'test_user', password: '123456' }) }); const data = await response.json(); // 登录成功后更新页面状态或跳转 } catch (err) { console.error('登录失败:', err); } };
- 获取用户信息(验证会话):
const fetchUserProfile = async () => { try { const response = await fetch('/api/profile', { credentials: 'include' }); if (response.status === 401) { // 未登录状态,跳转至登录页 window.location.href = '/login'; return; } const data = await response.json(); // 更新组件中的用户状态 } catch (err) { console.error('获取用户信息失败:', err); } };
CSRF防护补充
sameSite: strict已能覆盖大部分CSRF场景,生产环境建议再加一层令牌验证:
- 后端用
csurf库生成CSRF令牌,存在Session中并返回给前端 - 前端将令牌存在内存或LocalStorage中,非GET请求时携带在请求头
- 后端验证请求头令牌与Session中是否一致
示例代码:
安装依赖:
npm install csurf
后端配置:
const csurf = require('csurf'); const csrfProtection = csurf({ cookie: true }); app.use(csrfProtection); // 提供CSRF令牌的接口 app.get('/api/csrf-token', (req, res) => { res.json({ csrfToken: req.csrfToken() }); }); // 登录接口加入CSRF验证 app.post('/api/login', csrfProtection, (req, res) => { // 登录逻辑... });
前端处理:
// 页面加载时获取CSRF令牌 const getCsrfToken = async () => { const response = await fetch('/api/csrf-token', { credentials: 'include' }); const data = await response.json(); localStorage.setItem('csrfToken', data.csrfToken); }; // 登录请求携带令牌 const handleLogin = async () => { const csrfToken = localStorage.getItem('csrfToken'); try { const response = await fetch('/api/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ username: 'test_user', password: '123456' }) }); // ... } catch (err) { console.error(err); } };
为什么不推荐LocalStorage?
- LocalStorage存储的数据可被前端JS直接读取,一旦页面存在XSS漏洞,攻击者能直接窃取会话信息
- LocalStorage不会自动携带到请求中,需要手动处理,容易遗漏或出错
- 无内置过期机制,需自行实现会话过期逻辑
内容的提问来源于stack exchange,提问作者Dalvin Segura
相关产品推荐
相关产品推荐

