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

React+Express部署Railway后刷新页面认证Cookie丢失求助

生产环境刷新页面丢失JWT认证Cookie的问题

我用React和Express开发项目,本地环境刷新页面能正常维持登录状态,但部署到Railway平台后,已登录状态下刷新页面时认证Cookie会丢失,无法保持登录。已经做了以下配置但无效:

  • CORS设置了credentials: true并指定了前端源
  • Axios实例开启withCredentials: true
  • 尝试过设置Cookie的httpOnly: true、secure: true、sameSite: "None"属性

相关代码

Express端JWT生成函数

export const createAccessToken = async (payload) => {
  return new Promise((resolve, reject) => {
    jwt.sign(payload, TOKEN_SECRET, { expiresIn: "1d" }, (err, token) => {
      if (err) reject(err);
      resolve(token);
    });
  });
};

登录时设置Cookie的代码

const token = await createAccessToken({
      id: userFound._id,
    });

    res
      .cookie("access_token", token, {
        //httpOnly: true,
        secure: true,
        sameSite: "None",
        domain: DOMAIN,
      })
      .status(200)

CORS配置

app.use(
cors({
credentials: true,
origin: [FRONTEND_URL],
})
);

React端Axios实例

import axios from "axios";
import { API_URL } from "../config";

const instance = axios.create({
  baseURL: API_URL,
  withCredentials: true,
});

export default instance;

React AuthContext代码

import { useEffect } from "react";
import { createContext, useContext, useState } from "react";
import {
  loginRequest,
  registerRequest,
  verifyTokenRequest,
  logoutRequest,
  verifyEmailRequest,
  verifyEmailTokenRequest,
} from "../api/auth";
import Cookies from "js-cookie";

const AuthContext = createContext();

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) throw new Error("useAuth must be used within a AuthProvider");
  return context;
};

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState({});
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [errors, setErrors] = useState([]);
  const [loading, setLoading] = useState(true);

  // clear errors after 5 seconds
  useEffect(() => {
    if (errors.length > 0) {
      const timer = setTimeout(() => {
        setErrors([]);
      }, 5000);
      return () => clearTimeout(timer);
    }
  }, [errors]);

  const signup = async (user) => {
    try {
      const res = await registerRequest(user);
      if (res.status === 200) {
        setUser(res.data);
        setIsAuthenticated(true);
      }
    } catch (error) {
      console.log(error);
      setErrors(error.response.data.message);
    }
  };

  const signin = async (user) => {
    try {
      const res = await loginRequest(user);
      setUser(res.data);
      setIsAuthenticated(true);
    } catch (error) {
      console.log(error);
      setErrors(error.response.data.message);
    }
  };

  const verifyEmail = async (data) => {
    try {
      const res = await verifyEmailRequest(data);
      return res.data;
    } catch (error) {
      console.log(error);
      setErrors(error.response.data.message);
    }
  };

  const verifyEmailToken = async (data) => {
    try {
      const res = await verifyEmailTokenRequest(data);
      return res.data;
    } catch (error) {
      console.log(error);
      setErrors(error.response.data.message);
    }
  };

  const logout = async () => {
    Cookies.remove("access_token");
    setUser(null);
    setIsAuthenticated(false);
  };

  useEffect(() => {
    const checkLogin = async () => {
      const token = Cookies.get("access_token");
      if (!token) {
        setIsAuthenticated(false);
        setLoading(false);
        return;
      }

      try {
        const res = await verifyTokenRequest(token);
        if (!res.data) return setIsAuthenticated(false);
        setIsAuthenticated(true);
        setUser(res.data);
        setLoading(false);
      } catch (error) {
        setIsAuthenticated(false);
        setLoading(false);
      }
    };
    checkLogin();
  }, []);

  return (
    <AuthContext.Provider
      value={{
        user,
        signup,
        signin,
        verifyEmail,
        verifyEmailToken,
        logout,
        isAuthenticated,
        errors,
        loading,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default AuthContext;

问题排查与解决方案

1. Cookie的domain配置问题

Railway部署的服务会分配默认域名(如xxx.up.railway.app),如果你的DOMAIN变量设置的是自定义域名或与前端域名不匹配,会导致Cookie无法被浏览器保存。

  • 建议移除domain配置,让浏览器自动匹配当前请求的域名;若必须设置,确保和前端页面的主域名一致(比如前端是myapp.com,后端是api.myapp.com,则domain设为.myapp.com)。

2. 强制开启httpOnly属性

当前代码注释掉了httpOnly: true,这不仅不安全,还可能在跨域场景下出现存储问题。生产环境必须开启该属性,同时配合环境变量动态配置其他属性:

res.cookie("access_token", token, {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production", // 仅生产环境开启HTTPS强制
  sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
  maxAge: 24 * 60 * 60 * 1000, // 明确设置过期时间,与JWT有效期一致
});

3. 修复前端Token验证逻辑

当Cookie设置httpOnly: true时,前端JS无法读取Cookie,当前checkLogin函数通过Cookies.get("access_token")获取Token的逻辑会失效。

  • 后端验证接口改为从req.cookies.access_token获取Token,而非请求参数/body;
  • 前端verifyTokenRequest改为不带参数的请求,让Axios自动携带Cookie:
    // api/auth.js 中的verifyTokenRequest
    export const verifyTokenRequest = () => {
      return axios.get("/api/auth/verify");
    };
    
  • 修改AuthContext中的checkLogin函数:
    useEffect(() => {
      const checkLogin = async () => {
        try {
          const res = await verifyTokenRequest();
          setIsAuthenticated(true);
          setUser(res.data);
        } catch (error) {
          setIsAuthenticated(false);
        } finally {
          setLoading(false);
        }
      };
      checkLogin();
    }, []);
    

4. 确保HTTPS一致性

Railway默认提供HTTPS,若前端使用HTTP协议,secure: true的Cookie不会被保存,需确保前后端都使用HTTPS。

5. 精确匹配CORS origin

确保FRONTEND_URL是前端完整地址(如https://my-frontend.up.railway.app),不能带斜杠或使用通配符,建议用环境变量动态适配不同环境。


内容的提问来源于stack exchange,提问作者Isac Anderson Usucachi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 00:04:54