【问题标题】:Fetch with cookie not working even with `credentials: 'include'`即使使用 `credentials: 'include'` 也无法使用 cookie 获取
【发布时间】:2021-03-12 23:27:56
【问题描述】:

我无法通过 fetch 发送 cookie。我read 表示对于跨域请求,您必须使用credentials: 'include'。但这仍然没有给我饼干。

获取 html 文档

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div>fetch on load</div>
    <script>
      fetch('http://localhost:4001/sayhi', { credentials: 'include' })
        .then((res) => {
          return res.text();
        })
        .then((text) => {
          console.log('fetched: ' + text);
        })
        .catch(console.log);
    </script>
  </body>
</html>

服务器文件:

const express = require('express');
const mongoose = require('mongoose');

const session = require('express-session');
const MongoStore = require('connect-mongo')(session);

const app = express();
const cors = require('cors');

app.use(cors());
app.use((req, res, next) => {
  console.log(req.path);
 
  // this will log a cookie when several 
  // requests are sent through the browser 
  // but 'undefined' through `fetch`
  console.log('cookie in header: ', req.headers.cookie);
  
  next();
});

app.use(
  session({
    secret: 'very secret 12345',
    resave: true,
    saveUninitialized: false,
    store: new MongoStore({ mongooseConnection: mongoose.connection }),
  })
);

app.use(async (req, res, next) => {
  console.log(`${req.method}: ${req.path}`);
  try {
    req.session.visits = req.session.visits ? req.session.visits + 1 : 1;
    return next();
  } catch (err) {
    return next(err);
  }
});

app.get('/sayhi', (req, res, next) => {
  res.send('hey');
});

(async () =>
  mongoose.connect('mongodb://localhost/oneSession', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: true,
  }))()
  .then(() => {
    console.log(`Connected to MongoDB set user test`);
    app.listen(4001).on('listening', () => {
      console.log('info', `HTTP server listening on port 4001`);
    });
  })
  .catch((err) => {
    console.error(err);
  });

运行 console.log('cookie in header: ', req.headers.cookie); 的中间件为每个获取请求返回 undefined。

我相信每个 fetch 请求也在创建一个新会话,因为没有设置 cookie。

如何通过 fetch 获取要发送的 cookie?

更新:部分答案

到目前为止,我认为添加这些帮助:

//instead of app.use(cors());
app.use(
  cors({
    credentials: true,
    origin: 'http://localhost:5501', // your_frontend_domain, it's an example
  })
);

app.use((req, res, next) => {
   `res.header('Access-Control-Allow-Credentials', true);   
   res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5501');` // your_frontend_domain, it's an example
   next()
});

但我还有一个问题:

在 devtools 中,我转到“网络”并刷新 html 文件以再次发送请求。在那里我看到了响应标头。我可以看到 Set-Cookie 标头已发送,但有一个黄色三角形警告。

它说将sameSite 设置为true

我需要在会话选项中添加cookie: { sameSite: 'none' }

app.use(
  session({
    secret: 'very secret 12345',
    resave: true,
    cookie: { sameSite: 'none' },
    saveUninitialized: false,
    store: new MongoStore({ mongooseConnection: mongoose.connection }),
  })
);

在我这样做之后,cookie 收到了一个新的黄色三角形警告,说我需要在 cookie-secure: true 上设置另一个选项。但这意味着只有通过 HTTPS 发送的请求才会起作用。但我正在开发中,无法通过 https 发送。

【问题讨论】:

    标签: node.js mongodb express cookies fetch


    【解决方案1】:

    我为你写了一个例子。
    您不需要为 sameSite 属性设置“none”。

    {
      saveUninitialized: false,
      resave: false,
      secret: 'very secret 12345',
      cookie: {
        secure: false,
      }
    }
    

    前端服务器

    // server.js
    const express = require('express');
    const app = express();
    app.use(express.static("./public"));
    app.listen(5001);
    
    <!-- index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <script>
            fetch('http://localhost:4001/test', {
                method: "POST",
                credentials: "include"
            }).then(response => {
                return response.json()
            }).then(json => {
                alert('Received:  ' + JSON.stringify(json));
            })
        </script>
    </body>
    </html>
    

    后端服务器

    // other-server.js
    const express = require('express');
    const app = express();
    const session = require('express-session')
    var sess = {
      saveUninitialized: false,
      resave: false,
      secret: 'very secret 12345',
      cookie: {
        secure: false,
      }
    }
    
    const bodyParser = require('body-parser');
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({
        extended: true
    }));
    app.use(session(sess))
    app.use((req, res, next) => {
        res.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Accept, Content-Type")
        res.setHeader("Access-Control-Allow-Origin", "http://localhost:5001")
        res.setHeader("Access-Control-Allow-Credentials", true)
        res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH")
        next();
    })
    app.post('/test', (req, res) => {
        console.log(req.sessionID);
        req.session.a = "hi"
        res.json({a: 1})
    })
    
    app.listen(4001, () => {
        console.log('start');
    });
    

    【讨论】:

    • 这不起作用。 Cookie 仍未定义
    • 你填写的来源是什么?
    • origin: "http://127.0.0.1:5501" 我试过origin: "http://localhost:5501"。也许我没有正确设置 cookie 标题?我认为“快速会话”会为您完成。
    • 浏览器控制台是否显示任何消息?能否使用默认会话存储来避免 mongondb 未连接等其他情况?
    • 它只需要域名而不是ip,你可以看看CORSorigin
    【解决方案2】:

    更新

    当我在浏览器上导航到http://127.0.0.1:5501/index.html' 时,以下解决方案有效。但是,如果您导航到 localhost 而不是 127.0.0.1 Jack Yu's 答案有效。


    我需要在 `cors` 调用 `{ credentials: true, origin: 'http://localhost:5501' }` 中添加选项。以及 cookie 上的选项 `{ secure: false, sameSite: 'none'}`。以及设置`res.header`s。
    const express = require('express');
    
    const session = require('express-session');
    
    const app = express();
    const cors = require('cors');
    
    app.use(
      cors({
        credentials: true,
        origin: 'http://localhost:5501',
      })
    );
    
    app.use(
      session({
        saveUninitialized: false,
        resave: false,
        secret: 'very secret 12345',
        cookie: {
          secure: false,
          sameSite: 'none',
        },
      })
    );
    
    app.use(async (req, res, next) => {
      res.header('Access-Control-Allow-Credentials', true);
      res.header(
        'Access-Control-Allow-Headers',
        'Origin, X-Requested-With, Content-Type, Accept, authorization'
      );
      res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE,PUT,OPTIONS');
      res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5501');
    
      try {
        req.session.visits = req.session.visits ? req.session.visits + 1 : 1;
        console.log('req.session.visits: ', req.session.visits);
        return next();
      } catch (err) {
        return next(err);
      }
    });
    
    app.get('/sayhi', (req, res, next) => {
      res.send('hey');
    });
    
    app.listen(4001).on('listening', () => {
      console.log('info', `HTTP server listening on port 4001`);
    });
    
    

    这适用于火狐。但它仍然无法在 chrome 上运行,因为 Chrome 现在只提供带有跨站点请求的 cookie,前提是它们设置为 SameSite=NoneSecure。我的设置为secure: false,因为我没有通过 HTTPS 发送进行开发。所以我按照以下说明进行操作:

    您可以通过转到“chrome://flags”并禁用“没有 SameSite 的 Cookie 必须是安全的”来完全禁用此功能。但是,这将对所有网站禁用它,因此当您不进行开发时,它的安全性会降低。

    source

    但我担心没有更好的解决方案。如果出于安全原因,我不想为所有网站禁用 "Cookies without SameSite must be secure"

    【讨论】:

      猜你喜欢
      • 2021-08-09
      • 2020-04-01
      • 2016-11-27
      • 2017-04-28
      • 2015-11-24
      • 2023-02-13
      • 1970-01-01
      • 2022-12-08
      相关资源
      最近更新 更多