具有 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:3000
和 http://localhost:3000
,其中我的 React 客户端 运行s.
我尝试过的一些事情:
- 正在尝试
secure:true
和 secure:false
的所有组合以及 sameSite:false
和 sameSite:'strict'
的所有组合
- 将域设置为
NULL
或空字符串
- 正在尝试随机更改路径
这是我在后端登录时设置 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;
我测试过的更多内容:
- 我仔细检查了两边的凭据是否都设置为 true。
- 我已确保身份验证周期在其他浏览器上正常工作。
- 当我 运行 在 http 而不是 https
上做出反应时,我还确保身份验证周期在 Chrome 上有效
- 我还将我的自签名证书添加到本地计算机上的受信任根证书中。 Chrome 不再向我显示警告但仍拒绝保存 cookie
- 如果我 运行 禁用网络安全的 Chrome 实例,我已确保身份验证周期有效。
- 我试图通过在地址栏中使用 127.0.0.1 而不是 localhost 来使其工作,但无济于事。
- 双方的控制台均未记录任何错误。
我们将不胜感激
Chrome 总是用 cookie 和 localStorage 做一些疯狂的事情...
似乎因为 chrome 80 chrome 在使用跨站点请求时会拒绝任何未专门设置 SameSite=None
和 Secure
的 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";
确保:
- 您的 Path 变量设置为
/
。
- 您的域设置为
.<YOUR DOMAIN NAME>.com
(注意:这里.
点是必要的部分)。
- 你的 secure 变量应该是
true
.
- 您的 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(以及所有其他浏览器)上的问题。
我正在开发一个典型的 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:3000
和 http://localhost:3000
,其中我的 React 客户端 运行s.
我尝试过的一些事情:
- 正在尝试
secure:true
和secure:false
的所有组合以及sameSite:false
和sameSite:'strict'
的所有组合
- 将域设置为
NULL
或空字符串 - 正在尝试随机更改路径
这是我在后端登录时设置 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;
我测试过的更多内容:
- 我仔细检查了两边的凭据是否都设置为 true。
- 我已确保身份验证周期在其他浏览器上正常工作。
- 当我 运行 在 http 而不是 https 上做出反应时,我还确保身份验证周期在 Chrome 上有效
- 我还将我的自签名证书添加到本地计算机上的受信任根证书中。 Chrome 不再向我显示警告但仍拒绝保存 cookie
- 如果我 运行 禁用网络安全的 Chrome 实例,我已确保身份验证周期有效。
- 我试图通过在地址栏中使用 127.0.0.1 而不是 localhost 来使其工作,但无济于事。
- 双方的控制台均未记录任何错误。
我们将不胜感激
Chrome 总是用 cookie 和 localStorage 做一些疯狂的事情...
似乎因为 chrome 80 chrome 在使用跨站点请求时会拒绝任何未专门设置 SameSite=None
和 Secure
的 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";
确保:
- 您的 Path 变量设置为
/
。 - 您的域设置为
.<YOUR DOMAIN NAME>.com
(注意:这里.
点是必要的部分)。 - 你的 secure 变量应该是
true
. - 您的 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(以及所有其他浏览器)上的问题。