为何Refresh Token未以HTTP Cookie形式发送至服务器?
登录时已成功将refreshToken设置为Cookie(可在开发者工具Application面板查看),但发起创建产品请求时,尽管axios配置了withCredentials: true,后端checkAuth中间件中req.cookies.refreshToken仍为undefined,导致JWT过期后触发"JWT令牌过期,认证失败"错误。已配置跨域支持及Cookie接收,Cookie路径设为"/"、域为"localhost",问题仍存在。
登录接口代码
export const login = async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email: email }); console.log(user); if (!user) return res.status(400).json({ msg: "User does not exist. " }); const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) return res.status(400).json({ msg: "Invalid credentials. " }); let accessToken, refreshToken; try { accessToken = jwt.sign( { userId: user._id, email: user.email }, process.env.JWT_SECRET, { expiresIn: "2m" } ); refreshToken = jwt.sign( { userId: user._id }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: "7d" } ); const SetCookie = res.cookie("refreshToken", refreshToken, { httpOnly: true, expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days }) console.log(SetCookie) } catch (err) { console.error(err); res .status(500) .json({ message: "Signing up failed, please try again later." }); } res.status(200).json({ message: "logged in!", user, accessToken, refreshToken }); } catch (err) { console.error(err); res.status(500).json({ error: err.message }); } }
addProduct异步Thunk代码
export const addProduct = createAsyncThunk( 'product/addProduct', async (productData, { rejectWithValue }) => { try { const { user, token, formData } = productData; const url = `http://localhost:3005/product/${user._id}/add-product`; const config = { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'multipart/form-data', }, withCredentials: true, }; const response = await axios.post(url, formData, config,); console.log(response); const newProduct = response.data.product; return newProduct; } catch (error) { console.error(error); console.error(error?.response?.data ?? error.message); return rejectWithValue(error.response.data); } } );
checkAuth中间件代码
export const checkAuth = async (req, res, next) => { try { const authHeader = req.headers.authorization; if (!authHeader) { throw new Error("Authentication failed!"); } const token = authHeader.split(" ")[1]; // Authorization: Bearer <token> const decodedToken = jwt.verify(token, process.env.JWT_SECRET); req.userData = { userId: decodedToken.userId }; console.log(`DecodedToken Time : ${decodedToken.exp}`) console.log(`Start, ${token}, end`) const currentTime = Date.now() / 1000; // convert to seconds // const currentTime = Math.floor(Date.now() / 1000); // convert to seconds if (decodedToken.exp - currentTime < 60) { const rToke = req.headers.cookie const refreshToken = rToke.split('=')[1]; console.log(`refreshToken:${refreshToken}`) // Access token has expired, try to refresh using refresh token // console.log(refreshToken) if (!refreshToken) { throw new Error("Refresh token not found!"); } try { const decodedRefreshToken = jwt.verify( refreshToken, process.env.REFRESH_TOKEN_SECRET ); console.log(decodedRefreshToken, 'refresh token was verified!') } catch (error) { console.error(error) } // Validate the user from the decodedRefreshToken if necessary // Generate a new access token and refresh token const newAccessToken = jwt.sign( { userId: decodedToken.userId }, process.env.JWT_SECRET, { expiresIn: "3m" } ); const newRefreshToken = jwt.sign( { userId: decodedToken.userId }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: "7d" } ); res.cookie("refreshToken", newRefreshToken, { httpOnly: true }); // Send the new access token in the response headers console.log(newAccessToken) res.set("Authorization", `Bearer ${newAccessToken}`); // res.json({newAccessToken}) } else { const decodedToken = jwt.verify(token, process.env.JWT_SECRET); } next(); } catch (err) { const error = new HttpError('Authentication failed', 401); return next(error); } };
服务器跨域配置代码
app.use(cors({ origin: 'http://localhost:3000', credentials: true, cookie: { domain: 'localhost', }, })); app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); res.setHeader('Access-Control-Allow-Credentials', true); res.setHeader( 'Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization' ); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, OPTIONS, DELETE'); next(); }); app.use(cookieParser());
问题排查及修复方案
1. Cookie配置缺失关键参数
登录接口设置Cookie时,缺少跨域场景下的必要配置,导致浏览器拦截Cookie传递:
sameSite:跨域请求必须设置为none(生产环境)或lax(开发环境)path:明确指定为/,确保所有路径都能访问Cookiedomain:显式设置为localhost,避免浏览器自动判断错误
修改登录接口的Cookie设置:
const SetCookie = res.cookie("refreshToken", refreshToken, { httpOnly: true, expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days path: '/', domain: 'localhost', sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', secure: process.env.NODE_ENV === 'production' // 生产环境必须开启HTTPS })
2. 跨域配置重复冲突
当前同时使用cors中间件和手动设置响应头,可能导致配置冲突。保留cors中间件即可,简化配置:
// 删除手动设置响应头的中间件,仅保留以下配置 app.use(cors({ origin: 'http://localhost:3000', credentials: true, allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'], methods: ['GET', 'POST', 'PATCH', 'OPTIONS', 'DELETE'] })); app.use(cookieParser());
3. checkAuth中间件获取Cookie方式错误
不要手动拆分req.headers.cookie,直接使用cookieParser解析后的req.cookies:
// 替换原有的refreshToken获取逻辑 const refreshToken = req.cookies.refreshToken; console.log(`refreshToken:${refreshToken}`) if (!refreshToken) { throw new Error("Refresh token not found!"); }
4. 预检请求处理确认
跨域POST请求会先发送OPTIONS预检请求,确保cors中间件已自动处理该请求,或在配置中明确允许OPTIONS方法(上述简化后的cors配置已包含)。
内容的提问来源于stack exchange,提问作者novicecoder
相关产品推荐
相关产品推荐

