浏览器不存储来自express-sessions **更新的配置的React XHR请求的会话cookie

问题描述 投票:0回答:3

我正在使用 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 响应包含正确的

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

reactjs express axios xmlhttprequest session-cookies
3个回答
2
投票

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

回顾一下,后端服务器是使用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”上使用通配符将不起作用。


1
投票

这看起来不对:

axios.defaults.headers.common = {
  credentials: "include",
  withCredentials: true
}

没有这样的请求标头。相反,凭据是通过 XHR 请求控制的。

使用此选项来确保您的客户端接受 cookie:

 axios.defaults.withCredentials = true;

0
投票

就我而言,cors 配置的基本形式就足够了:允许源站点。

Essential 似乎在发出请求之前在前端 React 应用程序中设置

axios.defaults.withCredentials = true;

为了完整起见,这是来自 Laravel 应用程序的示例,在后端具有以下 cors 配置,其中创建了 cookie:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | (FruitCake) Laravel CORS Options
    |--------------------------------------------------------------------------
    |
    | The allowed_methods and allowed_headers options are case-insensitive.
    |
    | You don't need to provide both allowed_origins and allowed_origins_patterns.
    | If one of the strings passed matches, it is considered a valid origin.
    |
    | If ['*'] is provided to allowed_methods, allowed_origins or allowed_headers
    | all methods / origins / headers are allowed.
    |
    */

    /*
     * You can enable CORS for 1 or multiple paths.
     * Example: ['api/*']
     */
    'paths' => ['*'],

    /*
    * Matches the request method. `['*']` allows all methods.
    */
    'allowed_methods' => ['*'],

    /*
     * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`
     */
    'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS'))),

    /*
     * Patterns that can be used with `preg_match` to match the origin.
     */
    'allowed_origins_patterns' => [],

    /*
     * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers.
     */
    'allowed_headers' => ['*'],

    /*
     * Sets the Access-Control-Expose-Headers response header with these headers.
     */
    'exposed_headers' => [],

    /*
     * Sets the Access-Control-Max-Age response header when > 0.
     */
    'max_age' => 0,

    /*
     * Sets the Access-Control-Allow-Credentials header.
     */
    'supports_credentials' => true,
];
© www.soinside.com 2019 - 2024. All rights reserved.