跨域会话 Cookie(Heroku 上的 Express API + Netlify 上的 React App)

Posted

技术标签:

【中文标题】跨域会话 Cookie(Heroku 上的 Express API + Netlify 上的 React App)【英文标题】:Cross-Domain Session Cookie (Express API on Heroku + React App on Netlify) 【发布时间】:2021-06-04 19:05:38 【问题描述】:

我有一个 React 应用程序调用 node.js/Express 中的 API。

前端部署在 Netlify (https),​​后端部署在 Heroku (https)。

我的问题:

一切都在开发环境 (localhost) 中工作 在生产环境 (Netlify/Heroku) 中,注册和登录的 api 调用似乎有效,但 会话 cookie 未存储在浏览器中。因此,对 API 中受保护路由的任何其他调用都会失败(因为我没有收到用户凭据)。

             

说话很便宜,给我看代码....

后端(Express API):

我正在使用passport(本地策略)、express-session、cors。

App.js

require('./configs/passport');

// ...

const app = express();

// trust proxy (https://***.com/questions/64958647/express-not-sending-cross-domain-cookies)
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 端点(例如 item-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)
  
   
  //...

正在创建一个新项目...:

    axios.post(`$process.env.REACT_APP_API_URL/items`, 
        title: this.state.title,
        description: this.state.description,
    , withCredentials:true)
    .then( (res) => 
        // ...
    );

【问题讨论】:

【参考方案1】:

简答:

它没有按预期工作,因为我在 Chrome 隐身模式下进行测试,默认情况下,Chrome 在隐身模式下会阻止第三方 cookie (more details)。

下面是一个列表,其中包含一些检查您是否遇到类似问题的事项 ;)


清单

如果有帮助,这里有一份清单,其中包含您主要需要的不同东西;)

(后端)添加“信任代理”选项

如果您在 Heroku 上进行部署,请添加以下行(您可以在会话设置之前添加它)。

app.set("trust proxy", 1);

(后端)检查您的会话设置

特别是检查选项sameSitesecure (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,因此您可能想要这样的东西:

...并处理浏览器限制...:

最后,请记住,每个浏览器都有不同的第三方 cookie 政策,总的来说,这些限制预计在未来几年会增加。

例如,Chrome 预计将在 2023 年的某个时候阻止第三方 Cookie (source)。

如果您的应用需要绕过这些限制,这里有一些选项:

在同一个域下实现后端和前端

在同一个域的子域下实现Backend & Frontend(例如example.com & api.example.com)

让你的后端 API 在代理下(如果你使用 Netlify,你可以很容易地setup a proxy using a _redirects file)

【讨论】:

非常感谢,救命! 嘿,伙计,我尝试了存储 httpOnly cookie 以维护会话的方法,但浏览器没有在应用程序中存储 cookie,但我确实得到了它们作为响应,即当我登录时我创建了一个令牌并保存它在请求它的一个 httpONly 真正的令牌与相同的站点无和安全我也添加了信任代理之前 cors 和 cookie 解析器仍然无法工作 @MohammadFahad 检查最后 2 点(“检查第三方 cookie 的配置”和“......并处理浏览器限制......”)。如果这不能解决您的问题,最好的选择是发布不同的问题并提供代码,以便其他人可以帮助您;)

以上是关于跨域会话 Cookie(Heroku 上的 Express API + Netlify 上的 React App)的主要内容,如果未能解决你的问题,请参考以下文章

跨域 cookie 访问(或会话)

前后端分离(跨域)sessionid不一致Safari浏览器解决方案(不能保持会话或者不能存储cookie)

Heroku 上的 Django CSRF_COOKIE_DOMAIN

Heroku上的Socket.io“会话ID未知”错误

跨域登录

如何安全地进行跨域认证?