【问题标题】:Express doesn't set a cookieExpress 没有设置 cookie
【发布时间】:2016-08-17 20:55:48
【问题描述】:

我在通过 express 设置 cookie 时遇到问题。我正在使用Este.js dev stack 并尝试在API auth /login 路由中设置一个cookie。这是我在/api/v1/auth/login route 中使用的代码

res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999)});
res.status(200).send({user, token: jwt.token});

src/server/main.js我已经注册cookie-parser作为第一个中间件

app.use(cookieParser());

/api/v1/auth/login 路由的响应头包含

Set-Cookie:token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ.. 

但 cookie 未保存在浏览器中document.cookie 为空,开发者工具中的Resources - Cookies 选项卡也为空):(

编辑: 我发现当我在/api/v1/auth/login 中调用它时(没有调用res.sendres.json

res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false}); next();

然后设置 cookie 并且响应标头已设置 X-Powered-By:Este.js ...这会将 esteMiddleware 设置为 expres frontend rendering part

当我使用res.send

res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false}).send({user, token: jwt.token});`
next();

然后我得到错误Can't set headers after they are sent.,因为使用了send方法,所以前端渲染抛出这个错误。

但是我必须从 API 发送数据,我该如何处理呢?

【问题讨论】:

  • 你知道是document.cookie,而不是document.cookies吗?而且,当您查找 cookie 时,您是否在与 /api/v1/auth/login 发送到的域完全相同的页面中?
  • 抱歉输入错误,确保document.cookie 为空(已编辑)。是的,它是同一个域,一切都在http://localhost:8000/
  • @Mira cookie 在以后的请求中是否在服务器端可用 - req.cookies.tokenSet-Cookie 标头中的值后面还有哪些其他选项?
  • @JonathanLonowski 正如我所说,所有请求都来自同一个域。我之前也尝试在res.cookies 选项中更改httpOnly: false 但没有任何效果:(
  • 主要问题是cookie根本没有保存在浏览器中。

标签: node.js express cookies


【解决方案1】:

有几个问题:

  • 未通过httpOnly : false 明确设置的cookie 不能通过浏览器中的document.cookie 访问。它仍然会与 HTTP 请求一起发送,如果您检查浏览器的开发工具,您很可能会在那里找到 cookie(在 Chrome 中,它们可以在开发工具的 Resources 选项卡中找到);
  • 您所调用的 next() 仅应在您想推迟向应用程序的其他部分发送回响应时使用,根据您的代码判断,这不是您想要的。

所以,在我看来,这应该可以解决您的问题:

res.cookie('token', jwt.token, {
  expires  : new Date(Date.now() + 9999999),
  httpOnly : false
});
res.status(200).send({ user, token: jwt.token });

附带说明:httpOnly 默认为 true 是有原因的(以防止恶意 XSS 脚本访问会话 cookie 等)。如果你没有很好的理由可以通过客户端 JS 访问 cookie,请不要设置为false

【讨论】:

  • 我已经尝试使用此代码(参见第一篇文章),但开发工具的 Resources - Cookie 选项卡仍然为空。我不知道为什么 cookie 没有保存在浏览器中 :(
【解决方案2】:

我使用 express 4 和 node 7.4 和 Angular,我遇到了同样的问题,请帮忙:
a) 服务器端:在 app.js 文件中,我为所有响应提供标头,例如:

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

这个必须在所有路由器之前都有
我看到很多添加了这个标题:

res.header("Access-Control-Allow-Headers","*");
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');

但我不需要那个,

b) 当您定义 cookie 时,您需要添加 httpOnly: false,例如:

 res.cookie( key, value,{ maxAge: 1000 * 60 * 10, httpOnly: false });

c) 客户端:在发送 ajax 中您需要添加:“withCredentials: true”,例如:

$http({
     method: 'POST',
     url: 'url, 
     withCredentials: true,
     data : {}
   }).then(function(response){
        // code  
   }, function (response) {
         // code 
   });

【讨论】:

    【解决方案3】:

    我遇到了同样的问题。服务器响应带有 cookie 集:

    Set-Cookie:my_cookie=HelloWorld; Path=/; Expires=Wed, 15 Mar 2017 15:59:59 GMT 
    

    但是 cookie 没有被浏览器保存。

    我就是这样解决的。

    我在客户端代码中使用fetch。如果您未在 fetch 选项中指定 credentials: 'include',则 cookie 既不会发送到服务器,也不会被浏览器保存,尽管服务器响应会设置 cookie。

    例子:

    var headers = new Headers();
    headers.append('Content-Type', 'application/json');
    headers.append('Accept', 'application/json');
    
    return fetch('/your/server_endpoint', {
        method: 'POST',
        mode: 'same-origin',
        redirect: 'follow',
        credentials: 'include', // Don't forget to specify this if you need cookies
        headers: headers,
        body: JSON.stringify({
            first_name: 'John',
            last_name: 'Doe'
        })
    })
    

    【讨论】:

    • 你先生,是救生员!
    • 哇。 credentials 部分确实是问题所在。我整天都在敲头。谢谢!你能解释一下为什么需要这样做吗?
    • credentials: 'include' 就是答案。我还必须在我的后端正确配置 CORS,它运行良好。谢谢!!!
    • 请注意,这并不像“express”那样不设置cookie,而是浏览器不允许cookie,除非您使用“credentials”字段明确告诉它。请注意credentials: 'include' 将设置来自任何域的cookie,而credentials:'same-site' 将只设置来自与客户端相同域的cookie。 Source
    • 根据 MDN docs for fetch API,它实际上是 credentials: "same-origin"。阅读更多:developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
    【解决方案4】:

    主要功能之一是正确设置标题。

    对于 nginx:

    add-header Access-Control-Allow-Origin' 'domain.com';

    add_header 'Access-Control-Allow-Credentials' 'true';

    将此添加到您的网络服务器。

    然后像这样形成cookie:

    "cookie": {
            "secure": true, 
            "path": "/", 
            "httpOnly": true, 
            "hostOnly": true, 
            "sameSite": false, 
            "domain" : "domain.com"
        }
    

    从 express 获取 cookie 的最佳方法是使用 cookie-parser。

    【讨论】:

      【解决方案5】:

      仔细检查 cookie 的大小。

      对我来说,我生成身份验证令牌以存储在我的 cookie 中的方式导致 cookie 的大小随着后续登录尝试而增加,最终导致浏览器无法设置 cookie,因为它太大了。

      Browser cookie size cheat sheet

      【讨论】:

        【解决方案6】:

        如果客户端和服务器位于不同的域中,则无法设置 cookie。不同的子域是可行的,但不是不同的域和不同的端口。

        如果使用 Angular 作为您的前端,您可以简单地将所有请求发送到与您的 Angular 应用程序相同的域(因此应用程序将所有 API 请求发送给它自己)并在每个 HTTP API 请求 URL 中粘贴 /api/ - 通常已配置在你的 environment.ts 文件中:

        export const environment = {
          production: false,
          httpPhp: 'http://localhost:4200/api'
        }
        

        那么所有的HTTP请求都会使用environment.httpPhp + '/rest/of/path'

        然后您可以通过创建 proxy.conf.json 来代理这些请求,如下所示:

        {
          "/api/*": {
            "target": "http://localhost:5200",
            "secure": false,
            "changeOrigin": true,
            "pathRewrite": {
              "^/api": ""
            }
          }
        }
        

        然后将其添加到 ng serve:

        ng serve -o --proxy-config proxy.conf.json
        

        然后重新启动您的应用程序,它应该一切正常,假设您的服务器实际上在 HTTP 响应标头中使用 Set-Cookie。 (注意,在 diff 域上,即使服务器配置正确,您甚至都不会看到 Set-Cookie 响应标头)。

        【讨论】:

          【解决方案7】:

          app.post('/api/user/login',(req,res)=>{
          
              User.findOne({'email':req.body.email},(err,user)=>{
                  if(!user) res.json({message: 'Auth failed, user not found'})
                  
                  user.comparePassword(req.body.password,(err,isMatch)=>{
                      if(err) throw err;
                      if(!isMatch) return res.status(400).json({
                          message:'Wrong password'
                      });
                      user.generateToken((err,user)=>{
                          if(err) return res.status(400).send(err);
                          res.cookie('auth',user.token).send('ok')
                      })
                  }) 
              })
          });

          回复

          res.cookie('auth',user.token).send('ok')

          服务器响应正常,但 cookie 未存储在浏览器中

          解决办法:

          为 chrome 添加 Postman 拦截器扩展,允许 postman 在浏览器中存储 cookie 并使用请求返回。

          【讨论】:

            【解决方案8】:

            在 cookie 中将“httpOnly”设置为 true 没有问题。

            我对请求使用“request-promise”,客户端是“React”应用,但技术并不重要。请求是:

                var options = {
                    uri: 'http://localhost:4000/some-route',
                    method: 'POST',
                    withCredentials: true
                }
            
                request(options)
                    .then(function (response) {
                        console.log(response)
                    })
                    .catch(function (err) {
                        console.log(err)
                    });
            

            node.js(express)服务器上的响应是:

               var token=JSON.stringify({
              "token":"some token content"
            });
            res.header('Access-Control-Allow-Origin', "http://127.0.0.1:3000");
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            res.header( 'Access-Control-Allow-Credentials',true);
            var date = new Date();
            var tokenExpire = date.setTime(date.getTime() + (360 * 1000));
            res.status(201)
               .cookie('token', token, { maxAge: tokenExpire, httpOnly: true })
               .send();
            

            客户端发出请求,服务器设置cookie,浏览器(客户端)接收它(您可以在“开发工具上的“应用程序选项卡”中看到它),然后我再次向服务器和cookie发起请求位于请求中:“req.headers.cookie”,因此服务器可以访问以进行验证。

            【讨论】:

              【解决方案9】:

              为此苦苦挣扎了 3 小时,终于意识到,使用axios,我应该将withCredentials 设置为true,即使我只接收cookie。

              axios.defaults.withCredentials = true;

              【讨论】:

                【解决方案10】:

                我在跨源请求方面遇到了同样的问题,这是我修复它的方法。您需要专门告诉浏览器允许凭据。使用 axios,您可以指定它以允许在每个请求上使用凭据,例如 axios.defaults.withCredentials = true 但是这将被 CORS 策略阻止,您需要在您的 api 上指定凭据为 true,例如

                const corsOptions = {
                    credentials: true,
                    ///..other options
                  };
                
                app.use(cors(corsOptions));
                

                更新:这仅适用于本地主机 生产环境问题详细解答见我的回答here

                【讨论】:

                  【解决方案11】:

                  我在 Angular 应用程序中遇到了同样的问题。虽然我使用了 cookie 并没有在浏览器中设置

                  res.cookie("auth", token, {
                    httpOnly: true,
                    sameSite: true,
                    signed: true,
                    maxAge: 24 * 60 * 60 * 1000,
                  });
                  

                  为了解决这个问题,我在服务器端的app.js文件中添加了app.use(cors({ origin:true, credentials:true }));

                  在我的 Angular 客户端订单服务中,我添加了 {withCredentials: true} 作为第二个参数,当按照代码调用 http 方法时

                  getMyOrders() {
                  return this.http
                    .get<IOrderResponse[]>(this.SERVER_URL + '/orders/user/my-orders', {withCredentials: true})
                    .toPromise();}
                  

                  【讨论】:

                    【解决方案12】:

                    我也遇到了同样的问题。

                    代码是否在两个地方发生了变化:

                    1. 在客户端:
                       const apiData = await fetch("http://localhost:5000/user/login", 
                    {
                            method: "POST",
                            body: JSON.stringify(this.state),
                            credentials: "include", // added this part
                            headers: {
                              "Content-Type": "application/json",
                            },
                          })
                    
                    1. 在后端:
                       const corsOptions = {
                        origin: true, //included origin as true
                        credentials: true, //included credentials as true
                    };
                    
                    app.use(cors(corsOptions));
                    

                    【讨论】:

                      【解决方案13】:

                      提供的大多数答案都是更正,但无论您所做的配置如何,cookie 都不会轻易地从不同的域设置。在这个答案中,我假设您仍在本地开发中。

                      要设置cookie,您可以轻松使用上述任何配置或

                        res.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']); // setting multiple  cookies or 
                                res.cookie('token', { maxAge: 5666666, httpOnly: true })
                      

                      在从传入请求 req.headers 访问您的 cookie 时,两者都会设置您的 cookie。 就我而言,我的 cookie 没有设置,因为我的服务器在 http://localhost:7000/ 上运行,而前端在 http://127.0.0.1:3000/ 上运行,所以通过使前端在 http://localhost 上运行来进行简单的修复:改为 3000。

                      【讨论】:

                        猜你喜欢
                        • 2021-11-18
                        • 1970-01-01
                        • 2020-12-05
                        • 2019-10-29
                        • 1970-01-01
                        • 2020-08-23
                        • 2021-02-14
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多