具有 https 连接的 MERN 堆栈无法在 Chrome 上设置 cookie,但会在所有其他浏览器上设置它们

MERN stack with https connection is unable to set cookies on Chrome but sets them on all other browsers

我正在开发一个典型的 MERN 应用程序,并且我已经完成了身份验证周期。我的 NodeJS/Express 后端使用 'express-session' 和 'connect-mongodb-connection' 来创建和处理会话。 React 前端使用 'axios' 与 API 进行通信。身份验证周期适用于除 Chrome 以外的所有浏览器。对于所有其他浏览器,在 MongoDB 中成功创建了一个会话,在浏览器中设置了 cookie,并且我成功登录了一个会话。

但是当使用 Chrome 进行测试时,除了设置 cookie 的部分外,一切都运行良好。我在一天的时间里对此进行了严格的测试,我可以追踪 cookie 到它从后端发送的位置。但是 Chrome 拒绝保存 cookie。

这是我维护会话的代码:

server/app.js

var store = new MongoDBStore({
  uri: DB,
  collection: 'sessions'

});

// Catch errors
store.on('error', function (error) {
  console.log(error);
});

app.use(require('express-session')({
  secret: process.env.SESSION_SECRET,
  saveUninitialized: false, // don't create session until something stored
  resave: false, //don't save session if unmodified
  store: store,
  cookie: {
    maxAge: parseInt(process.env.SESSION_LIFETIME), // 1 week
    httpOnly: true,
    secure: !(process.env.NODE_ENV === "development"),
    sameSite: false
  },
}));
//Mongo Session Logic End

app.enable('trust proxy');

// 1) GLOBAL MIDDLEWARES
// Implement CORS
app.use(cors({
  origin: [
    process.env.CLIENT_ORIGINS.split(',')
  ],
  credentials: true,
  exposedHeaders: ['set-cookie']
}));

CLIENT_ORIGINS 设置为 https://localhost:3000http://localhost:3000,其中我的 React 客户端 运行s.

我尝试过的一些事情:

  1. 正在尝试 secure:truesecure:false 的所有组合以及 sameSite:falsesameSite:'strict'
  2. 的所有组合
  3. 将域设置为 NULL 或空字符串
  4. 正在尝试随机更改路径

这是我在后端登录时设置 cookie 的代码:

exports.signIn = async (req, res, next) => {
  const { email, password } = req.body;
  if (signedIn(req)) {
    res.status(406).json('Already Signed In');
    return;
  }
  const user = await User.findOne({ email: email });
  if (!user) {
    res.status(400).json('Please enter a correct email.');
    return;
  }
  if (!(await user.matchPassword(password))) {
    res.status(400).json('Please enter a correct password.');
    return;
  }
  req.session.userId = user.id;
  res.status(200).json({ msg: 'Signed In', user: user });
};

这是我使用 Axios 从 React 调用 API 的通用请求模型:

import axios from "axios";
import CONFIG from "../Services/Config";

axios.defaults.withCredentials = true;
const SERVER = CONFIG.SERVER + "/api";

let request = (method, extension, data = null, responseTypeFile = false) => {
  //setting up headers
  let config = {
    headers: {
      "Content-Type": "application/json",
    },
  };
  // let token = localStorage["token"];
  // if (token) {
  //     config.headers["Authorization"] = `Bearer ${token}`;
  // }

  //POST Requests
  if (method === "post") {
    // if (responseTypeFile) {
    //     config['responseType'] = 'blob'
    // }
    // console.log('request received file')
    // console.log(data)
    return axios.post(`${SERVER}/${extension}`, data, config);
  }
  //PUT Requests
  else if (method === "put") {
    return axios.put(`${SERVER}/${extension}`, data, config);
  }
  //GET Requests
  else if (method === "get") {
    if (data != null) {
      return axios.get(`${SERVER}/${extension}/${data}`, config);
    } else {
      return axios.get(`${SERVER}/${extension}`, config);
    }
  }
  //DELETE Requests
  else if (method === "delete") {
    if (data != null) {
      return axios.delete(`${SERVER}/${extension}/${data}`, config);
    } else {
      return axios.delete(`${SERVER}/${extension}`, config);
    }
  }
};

export default request;

我测试过的更多内容:

  1. 我仔细检查了两边的凭据是否都设置为 true。
  2. 我已确保身份验证周期在其他浏览器上正常工作。
  3. 当我 运行 在 http 而不是 https
  4. 上做出反应时,我还确保身份验证周期在 Chrome 上有效
  5. 我还将我的自签名证书添加到本地计算机上的受信任根证书中。 Chrome 不再向我显示警告但仍拒绝保存 cookie
  6. 如果我 运行 禁用网络安全的 Chrome 实例,我已确保身份验证周期有效。
  7. 我试图通过在地址栏中使用 127.0.0.1 而不是 localhost 来使其工作,但无济于事。
  8. 双方的控制台均未记录任何错误。

我们将不胜感激

Chrome 总是用 cookie 和 localStorage 做一些疯狂的事情...

似乎因为 chrome 80 chrome 在使用跨站点请求时会拒绝任何未专门设置 SameSite=NoneSecure 的 cookie。那个问题 https://github.com/google/google-api-javascript-client/issues/561 仍然开放并在那里进行讨论。我还认为在不设置 Secure 的情况下使用 https 也会被拒绝。

我曾经遇到过同样的问题,我已经通过下面提到的具体设置解决了它:

document.cookie = "access_token=" + "<YOUR TOKEN>" + ";path=/;domain=."+ "<YOUR DOMAIN NAME>" +".com;secure;sameSite=none";

确保:

  1. 您的 Path 变量设置为 /
  2. 您的设置为.<YOUR DOMAIN NAME>.com注意:这里.点是必要的部分)。
  3. 你的 secure 变量应该是 true.
  4. 您的 sameSite 变量应该是 none.

所以我找到了解决问题的方法。我的客户端 运行 正在使用 https 连接(甚至在开发期间),因为我的项目的性质要求如此。

经过大量研究,我确信用于 express-session 的设置是这些:

app.use(require('express-session')({
  secret: process.env.SESSION_SECRET,
  saveUninitialized: false, // don't create session until something stored
  resave: false, //don't save session if unmodified
  store: store,
  cookie: {
    maxAge: parseInt(process.env.SESSION_LIFETIME), // 1 week
    httpOnly: true,
    secure: true,
    sameSite: "none"
  },
}));

请记住,即使在开发过程中,我的客户端也在 运行 上使用 https 连接。然而,尽管使用了这些设置,我的登录周期在 Chrome 上不起作用,我的 cookie 也没有被设置。

Express 会话拒绝向客户端发回 cookie,因为尽管我的客户端 运行 使用 https 连接,但它通过 http 连接联系我的服务器(我的服务器仍然 运行ning在开发中的 http 连接上),因此使连接不安全。

所以我将以下代码添加到我的服务器:

const https = require('https');
const fs = require('fs');

var key = fs.readFileSync("./certificates/localhost.key");
var cert = fs.readFileSync("./certificates/localhost.crt");
var credentials = {
  key,
  cert
};

const app = express();

const port = process.env.PORT || 3080;

const server = process.env.NODE_ENV === 'development' ? https.createServer(credentials, app) : app;
server.listen(port, () => {
  console.log(`App running on port ${port}...`);
});

在开发过程中,我在 https 连接上使用了自签名证书 运行 我的服务器。这与 sameSite: "none"secure: true 一起解决了 Chrome(以及所有其他浏览器)上的问题。