首页 > 解决方案 > 浏览器不存储来自 express-sessions 的 React XHR 请求的会话 cookie ** 更新的配置

问题描述

我正在使用 React 前端登录到运行 express-session 的 nodejs 服务器。前端在 localhost:3000 上运行,服务器在 localhost:5000 上。

使用来自本地主机的邮递员一切正常(当用户通过邮递员正确身份验证和接收/存储时,会话 cookie 从服务器发送。随后对服务器上不同路径的邮递员 api 请求使用会话 cookie 并正确检索它应该基于的数据会议内容)。我也可以使用浏览器直接登录到服务器(http://localhost:5000/api/authenticate)。服务器生成会话,将 cookie 发送到浏览器,并将 cookie 存储在本地。

当我从 React 应用程序中发出 api 请求时不起作用。服务器正在返回会话 cookie,但浏览器没有存储它。在过去几天对此进行研究之后(关于这个一般主题有很多问题),这似乎是跨站点请求的问题,但我似乎无法找到正确的应用程序和服务器设置集来获取它好好工作。cookie 是由服务器发送的,但是当应用程序发出请求时浏览器不会存储它。

*** 经过一些额外的故障排除和研究,我做了一些更新。我最初的 XHR 请求需要预先飞行,请求和响应标头现在似乎是正确的,但浏览器中仍然没有存储 cookie。设置下方的更多详细信息****

服务器设置

var corsOptions = {
  origin: 'http://localhost:3000',
  credentials: true
};

app.options('*', cors(corsOptions)) // for pre-flight

app.use(cors(corsOptions));

app.use(session({
  genid: (req) => {
    console.log('Inside the session middleware');
    console.log(req.sessionID);
    return uuidv4();
  },
  store: new FileStore(),
  secret: 'abc987',
  resave: false,
  saveUninitialized: true,
  cookie: { httpOnly: false, sameSite: 'Lax', hostOnly: false }
}));


app.use( bodyParser.json() );
app.use(bodyParser.urlencoded({
  extended: true
}));

app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, withCredentials, credentials');
  next();
});


app.post('/api/authenticate', function(req, res) {

  const usernameLower = req.body.username.toLowerCase();
  const passwordHash = md5(req.body.password);

  connection.query('select USERID from USERS where LOWER(USERNAME)=? && PASSWORD=? ', [usernameLower, passwordHash], function (error, results, fields) {
    if (error) {
      console.log(error);
      req.session.destroy();
      res.status(500)
        .json({
          error: 'Internal error please try again'
        });

    } else if (results[0]) {
          const userId = results[0].USERID;

          // setup session data
          mySession = req.session;
          mySession.user = {};
          mySession.user.userId = userId;
 
          res.json(mySession.user);

    } else {
      console.log('auth failed');
      req.session.destroy();
      res.status(401)
        .json({
          error: 'Incorrect email or password'
        });
    }
  });
});

客户端设置——通过单击表单中的提交按钮触发请求

  handleSubmit(event) {
    event.preventDefault();
    axios.defaults.withCreditials = true;
    axios.defaults.credentials = 'include';

    axios({
      credentials: 'include',
      method: 'post',
      url: 'http://localhost:5000/api/authenticate/',
      headers: {'Content-Type': 'application/json' },
      data: {
          username: this.state.username,
          password: this.state.password
        }
      })
      .then((response) => {
        if (response.status === 200) {
          this.props.setLoggedIn(true);
          console.log('userId: '+response.data.userId);
        } else {
          console.log("login error");
        }
     })
      .catch(error => console.log(error))
  }

下面是发送到浏览器的响应 cookie,但浏览器没有存储它。

{"connect.sid":{"path":"/","samesite":"Lax","value":"s:447935ac-fc08-47c6-9b66-4fa30b355021.Yo5H3XVz3Ux3GjTPVhy8i2ZPJm2RM2RzUnznxU9wBvo"}}

来自 XHR 请求的请求标头(飞行前):

OPTIONS /api/authenticate/ HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Referer: http://localhost:3000/
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive

飞行前服务器响应标头

HTTP/1.1 204 No Content
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin, Access-Control-Request-Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: content-type
Content-Length: 0
Date: Fri, 10 Jul 2020 21:35:05 GMT
Connection: keep-alive

POST 请求标头

POST /api/authenticate/ HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 45
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/

服务器响应标头

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Content-Type: application/json; charset=utf-8
Content-Length: 95
ETag: W/"5f-Iu5VYnDYPKfn7WPrRi2d2Q168ds"
Set-Cookie: connect.sid=s%3A447935ac-fc08-47c6-9b66-4fa30b355021.Yo5H3XVz3Ux3GjTPVhy8i2ZPJm2RM2RzUnznxU9wBvo; Path=/; SameSite=Lax
Date: Fri, 10 Jul 2020 21:35:05 GMT
Connection: keep-alive

我在https://httptoolkit.tech/will-it-cors/使用了“Will it CORS”工具,我的请求/响应标头似乎都是正确的,但仍然没有存储 cookie。

飞行前请求包含正确的来源 飞行前响应包含正确的允许来源和允许凭据 POST 请求包含正确的来源和允许凭据 POST 响应包含正确

感谢任何帮助解开这个问题....

标签: reactjsexpressaxiosxmlhttprequestsession-cookies

解决方案


我解决了我的问题并想发布解决方案以防其他人遇到此问题。

回顾一下,后端服务器是使用 express 的 nodejs。以下设置允许前端接受在 nodejs 服务器上创建的 cookie。

app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "https://frontendserverdomain.com:3000"); // update to match the domain you will make the request from
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    res.header("Access-Control-Allow-Credentials", true); // allows cookie to be sent
    res.header("Access-Control-Allow-Methods", "GET, POST, PUT, HEAD, DELETE"); // you must specify the methods used with credentials. "*" will not work. 
    next();
});

前端应用基于 React,使用 axios 发起 http 请求。它托管在“https://frontendserverdomain.com:3000”,它被添加到 nodejs 设置中的“Access-Control-Allow-Origin”标头中(见上文)。

在前端,Axios 需要应用“withCredentials”设置。

axios.defaults.withCredentials = true;

通过这些设置,您的应用将能够与后端服务器交换 cookie。

我让 CORS 工作的一个问题是确保将前端主机正确添加到后端服务器标题“Access-Control-Allow-Origin”。这包括端口号(如果在访问前端时在您的 URL 中指定)。

在 cookie 交换方面,“Access-Control-Allow-Credentials”和“Access-Control-Allow-Methods”标头必须如上所示正确设置。在“Access-Control-Allow-Methods”上使用通配符将不起作用。


推荐阅读