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

为何Refresh Token未以HTTP Cookie形式发送至服务器?

问题排查:跨域请求中Refresh Token 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:明确指定为/,确保所有路径都能访问Cookie
  • domain:显式设置为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 09:23:09