跨域会话 Cookie(Heroku 上的 Express API + Netlify 上的 React App)
Cross-Domain Session Cookie (Express API on Heroku + React App on Netlify)
我有一个 React 应用程序调用节点中的 API。js/Express。
前端部署在 Netlify (https),后端部署在 Heroku (https)。
我的问题:
- 一切都在开发环境(本地主机)中工作
- 在生产中 (Netlify/Heroku),api 注册和登录调用似乎有效,但 会话 cookie 未存储在浏览器中 。因此,API 中对受保护路由的任何其他调用都会失败(因为我没有收到用户凭据)。
说话便宜,给我看代码....
后端(Express API):
- 我正在使用 passport (local strategy), express-session, cors。
App.js
require('./configs/passport');
// ...
const app = express();
// trust proxy (
app.set("trust proxy", 1);
app.use(
session({
secret: process.env.SESSION_SECRET,
cookie: {
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax',
maxAge: 60000000,
secure: process.env.NODE_ENV === "production",
},
resave: true,
saveUninitialized: false,
ttl: 60 * 60 * 24 * 30
})
);
app.use(passport.initialize());
app.use(passport.session());
// ...
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
//...
app.use('/api', require('./routes/auth-routes'));
app.use('/api', require('./routes/item-routes'));
CRUD 端点(例如项目-routes.js):
// Create new item
router.post("/items", (req, res, next) => {
Item.create({
title: req.body.title,
description: req.body.description,
owner: req.user._id // <-- AT THIS POINT, req.user is UNDEFINED
})
.then(
// ...
);
});
前端(React 应用程序):
- 使用 Axios 并将“withCredentials”选项设置为 true...
用户注册登录:
class AuthService {
constructor() {
let service = axios.create({
baseURL: process.env.REACT_APP_API_URL,
withCredentials: true
});
this.service = service;
}
signup = (username, password) => {
return this.service.post('/signup', {username, password})
.then(response => response.data)
}
login = (username, password) => {
return this.service.post('/login', {username, password})
.then(response => response.data)
}
//...
}
正在创建新项目...:[=58=]
axios.post(`${process.env.REACT_APP_API_URL}/items`, {
title: this.state.title,
description: this.state.description,
}, {withCredentials:true})
.then( (res) => {
// ...
});
简答:
它没有按预期工作,因为我在 Chrome 隐身模式下进行测试,默认情况下,Chrome 在隐身模式下阻止第三方 cookie (more details).
如果您遇到类似问题,请在下面列出一些事项;)
清单
如果有帮助,这里有一份清单,其中包含您主要需要的不同内容;)
- (后端)添加“信任代理”选项
如果您在 Heroku 上部署,请添加以下行(您可以在会话设置之前添加它)。
app.set("trust proxy", 1);
- (后端)检查您的会话设置
特别是检查选项 sameSite
和 secure
(more details here)。
下面的代码将在生产环境中设置 sameSite: 'none'
和 secure: true
:
app.use(
session({
secret: process.env.SESSION_SECRET || 'Super Secret (change it)',
resave: true,
saveUninitialized: false,
cookie: {
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax', // must be 'none' to enable cross-site delivery
secure: process.env.NODE_ENV === "production", // must be true if sameSite='none'
}
})
);
- (后端)CORS 配置
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
- (后端)环境变量
在 Heroku 中设置环境变量。例如:
FRONTEND_APP_URL = https://my-project.netlify.app
重要:对于 CORS URL,避免在末尾使用斜杠。以下可能无效:
FRONTEND_APP_URL = https://my-project.netlify.app/ --> avoid this trailing slash!
- (前端)发送凭据
确保您在 API 调用中发送凭据(您需要对向 API 发出的所有调用执行此操作,包括用户登录调用)。
如果您使用的是 axios,则可以使用 withCredentials
选项。例如:
axios.post(`${process.env.REACT_APP_BACKEND_API_URL}/items`, {
title: this.state.title,
description: this.state.description,
}, {withCredentials:true})
.then( (res) => {
// ...
});
- (浏览器)检查第三方 cookie 的配置
为了进行测试,您可能希望确保使用的是每个浏览器提供的默认配置。
例如,自 2021 年起,Chrome 会在隐身模式下阻止第三方 cookie(但不会在“正常”模式下),因此您可能想要这样的东西:
- ...并处理浏览器限制...:[=99=]
最后,请记住每个浏览器对第三方 cookie 的政策不同,一般来说,这些限制预计在未来几年会增加。
例如,Chrome 预计将在 2023 年的某个时候阻止第三方 cookie (source)。
如果您的应用需要绕过这些限制,这里有一些选择:
在同一域下实现后端和前端
在同一域的子域下实现后端和前端(例如,example.com & api.example.com)
将您的后端 API 置于代理之下(如果您使用的是 Netlify,则可以轻松 setup a proxy using a _redirects file)
我有一个 React 应用程序调用节点中的 API。js/Express。
前端部署在 Netlify (https),后端部署在 Heroku (https)。
我的问题:
- 一切都在开发环境(本地主机)中工作
- 在生产中 (Netlify/Heroku),api 注册和登录调用似乎有效,但 会话 cookie 未存储在浏览器中 。因此,API 中对受保护路由的任何其他调用都会失败(因为我没有收到用户凭据)。
说话便宜,给我看代码....
后端(Express API):
- 我正在使用 passport (local strategy), express-session, cors。
App.js
require('./configs/passport');
// ...
const app = express();
// trust proxy (
app.set("trust proxy", 1);
app.use(
session({
secret: process.env.SESSION_SECRET,
cookie: {
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax',
maxAge: 60000000,
secure: process.env.NODE_ENV === "production",
},
resave: true,
saveUninitialized: false,
ttl: 60 * 60 * 24 * 30
})
);
app.use(passport.initialize());
app.use(passport.session());
// ...
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
//...
app.use('/api', require('./routes/auth-routes'));
app.use('/api', require('./routes/item-routes'));
CRUD 端点(例如项目-routes.js):
// Create new item
router.post("/items", (req, res, next) => {
Item.create({
title: req.body.title,
description: req.body.description,
owner: req.user._id // <-- AT THIS POINT, req.user is UNDEFINED
})
.then(
// ...
);
});
前端(React 应用程序):
- 使用 Axios 并将“withCredentials”选项设置为 true...
用户注册登录:
class AuthService {
constructor() {
let service = axios.create({
baseURL: process.env.REACT_APP_API_URL,
withCredentials: true
});
this.service = service;
}
signup = (username, password) => {
return this.service.post('/signup', {username, password})
.then(response => response.data)
}
login = (username, password) => {
return this.service.post('/login', {username, password})
.then(response => response.data)
}
//...
}
正在创建新项目...:[=58=]
axios.post(`${process.env.REACT_APP_API_URL}/items`, {
title: this.state.title,
description: this.state.description,
}, {withCredentials:true})
.then( (res) => {
// ...
});
简答:
它没有按预期工作,因为我在 Chrome 隐身模式下进行测试,默认情况下,Chrome 在隐身模式下阻止第三方 cookie (more details).
如果您遇到类似问题,请在下面列出一些事项;)
清单
如果有帮助,这里有一份清单,其中包含您主要需要的不同内容;)
- (后端)添加“信任代理”选项
如果您在 Heroku 上部署,请添加以下行(您可以在会话设置之前添加它)。
app.set("trust proxy", 1);
- (后端)检查您的会话设置
特别是检查选项 sameSite
和 secure
(more details here)。
下面的代码将在生产环境中设置 sameSite: 'none'
和 secure: true
:
app.use(
session({
secret: process.env.SESSION_SECRET || 'Super Secret (change it)',
resave: true,
saveUninitialized: false,
cookie: {
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax', // must be 'none' to enable cross-site delivery
secure: process.env.NODE_ENV === "production", // must be true if sameSite='none'
}
})
);
- (后端)CORS 配置
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
- (后端)环境变量
在 Heroku 中设置环境变量。例如:
FRONTEND_APP_URL = https://my-project.netlify.app
重要:对于 CORS URL,避免在末尾使用斜杠。以下可能无效:
FRONTEND_APP_URL = https://my-project.netlify.app/ --> avoid this trailing slash!
- (前端)发送凭据
确保您在 API 调用中发送凭据(您需要对向 API 发出的所有调用执行此操作,包括用户登录调用)。
如果您使用的是 axios,则可以使用 withCredentials
选项。例如:
axios.post(`${process.env.REACT_APP_BACKEND_API_URL}/items`, {
title: this.state.title,
description: this.state.description,
}, {withCredentials:true})
.then( (res) => {
// ...
});
- (浏览器)检查第三方 cookie 的配置
为了进行测试,您可能希望确保使用的是每个浏览器提供的默认配置。
例如,自 2021 年起,Chrome 会在隐身模式下阻止第三方 cookie(但不会在“正常”模式下),因此您可能想要这样的东西:
- ...并处理浏览器限制...:[=99=]
最后,请记住每个浏览器对第三方 cookie 的政策不同,一般来说,这些限制预计在未来几年会增加。
例如,Chrome 预计将在 2023 年的某个时候阻止第三方 cookie (source)。
如果您的应用需要绕过这些限制,这里有一些选择:
在同一域下实现后端和前端
在同一域的子域下实现后端和前端(例如,example.com & api.example.com)
将您的后端 API 置于代理之下(如果您使用的是 Netlify,则可以轻松 setup a proxy using a _redirects file)