Safari没有使用JS Fetch API设置CORS cookie

时间:2017-02-17 15:39:54

标签: javascript cookies safari cors fetch

在使用Fetch API时(实际上,通过fetch polyfill),我无法让Safari在服务器响应中成功应用Set-Cookie。相同的代码在FF和Chrome中正常工作(我使用native和polyfill fetch进行了测试。)

  1. 请求跨域;
  2. 是的,我正在设置credentials: true;
  3. 服务器确实以Set-Cookie标题回复;
  4. 后续请求是从Chrome和FF发送的,带有Cookie请求标头,但Safari没有;
  5. 请求使用HTTPS(证书是自签名的并且在开发域上,但似乎是Safari在常规请求中接受);和
  6. 有人知道问题可能是什么吗?

    我已阅读文档并浏览了许多closed bug reports。除非我错过了什么,否则我认为可能问题在于'default browser behaviour'处理cookie和CORS - 而不是fetch(通过polyfill源代码阅读,它似乎100%无知cookie)。一些错误报告表明,错误的服务器响应可能会阻止cookie被保存。

    我的代码如下所示:

    function buildFetch(url, init={}) {
        let headers = Object.assign({}, init.headers || {}, {'Content-Type': 'application/json'});
        let params = Object.assign({}, init, { credentials: 'include', headers });
    
        return fetch(`${baseUrl}${url}`, params);
    }
    
    buildFetch('/remote/connect', {method: 'PUT', body: JSON.stringify({ code })})
    .then(response => response.json())
    .then(/* complete authentication */)
    

    实际授权请求如下。我正在使用cURL来获取确切的请求/响应数据,因为Safari很难复制/粘贴它。

    curl 'https://mydevserver:8443/api/v1/remote/connect' \
    -v \
    -XPUT \
    -H 'Content-Type: application/json' \
    -H 'Referer: http://localhost:3002/' \
    -H 'Origin: http://localhost:3002' \
    -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8' \
    --data-binary '{"token":"value"}'
    
    
    *   Trying 127.0.0.1...
    * Connected to mydevserver (127.0.0.1) port 8443 (#0)
    * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    * Server certificate: mydevserver
    > PUT /api/v1/remote/connect HTTP/1.1
    > Host: mydevserver:8443
    > Accept: */*
    > Content-Type: application/json
    > Referer: http://localhost:3002/
    > Origin: http://localhost:3002
    > User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8
    > Content-Length: 15
    > 
    * upload completely sent off: 15 out of 15 bytes
    < HTTP/1.1 200 OK
    < Access-Control-Allow-Origin: http://localhost:3002
    < Access-Control-Allow-Credentials: true
    < Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Api-Key, Device-Key
    < Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
    < Access-Control-Expose-Headers: Date
    < Content-Type: application/json; charset=utf-8
    < Content-Length: 37
    < Set-Cookie: express:sess=[SESSIONKEY]=; path=/; expires=Fri, 17 Feb 2017 15:30:01 GMT; secure; httponly
    < Set-Cookie: express:sess.sig=[SIGNATURE]; path=/; expires=Fri, 17 Feb 2017 15:30:01 GMT; secure; httponly
    < Date: Fri, 17 Feb 2017 14:30:01 GMT
    < Connection: keep-alive
    < 
    * Connection #0 to host mydevserver left intact
    {"some":"normal","response":"payload"}
    

2 个答案:

答案 0 :(得分:15)

回答我自己的问题。

我觉得这非常令人激动,这是Safari的“按预期工作”行为,尽管我理解他们的动机。 XHR(当它本地登陆时可能是原生取件)根本不支持设置第三方cookie。这种失败是完全透明的,因为它是由脚本环境之外的浏览器处理的,因此基于客户端的解决方案实际上是不可能的。

您可以在此处找到一个推荐的解决方案,即在API服务器上的HTML页面上打开一个窗口或iframe,并在那里设置一个cookie。此时,第三方cookie将开始工作。这非常难看,并且无法保证Safari在某些时候不会关闭这个漏洞。

我的解决方案是基本上重新实现会话cookie执行的身份验证系统。即:

  1. 添加一个新标头X-Auth: [token],其中[token]是一个非常小的,短命的JWT,包含您的会话所需的信息(理想情况下只有用户ID - 不太可能在应用程序的生命周期内发生变异 - 但如果在会话期间可以更改权限,则绝对不是权限;
  2. X-Auth添加到Access-Control-Allow-Headers;
  3. 在登录期间,使用您需要的有效负载设置会话cookie和身份验证令牌(Safari和非Safari用户都将获得cookie和auth标头);
  4. 在客户端上,查找X-Token响应标头,并在它看到它时将其作为X-Token请求标头回显(您可以通过使用本地存储实现持久性 - 令牌过期,因此,即使价值存在多年,也不能在某一点之后兑现;)
  5. 在服务器上,对于受保护资源的所有请求,检查cookie并使用它(如果存在);
  6. 否则(如果cookie不存在 - 因为Safari没有发送它),查找标头令牌,验证并解码令牌有效负载,使用提供的信息更新当前会话,然后生成新的身份验证令牌,将其添加到响应标头中;
  7. 正常进行。
  8. 请注意,JWT(或任何类似的)旨在解决完全不同的问题,并且由于“重放”问题,应该永远不会用于会话管理(想想如果用户有两个自己打开的窗口会发生什么首部的状态)。但是,在这种情况下,它们提供了您通常需要的瞬间和安全性。最重要的是,您应该在支持它们的浏览器上使用cookie,尽可能减少会话信息,尽可能缩短您的JWT,并构建您的服务器应用程序,以期待意外和恶意重播攻击。

答案 1 :(得分:4)

仅供参考,尝试此〜18个月后,此解决方案对我不起作用。或者,对于某些用户而言,它似乎是断断续续的,真是太不可思议了。

  

一个推荐的解决方案是打开一个窗口或   iframe到API服务器上的HTML页面并在其中设置Cookie。在   至此,第三方Cookie将开始起作用。这很丑   并且无法保证Safari在某个时候不会关闭   漏洞。

最好的猜测是Safari内部使用的逻辑取决于您获取Cookie的顺序,或者更复杂,更不透明的事物。

我最终选择的解决方案是使用DNS,这可能是一个选择,因为您是从不同于API的主机提供React App的服务,或者以其他方式控制这两个站点, >

我们的客户来自www.company-name.com,而我们的API位于company-name.herokuapp.com。通过制作 CNAME 记录 api .company-name.com-> company-name.herokuapp.com,并将该域的该子域用于来自作为API的客户端,Safari不再考虑将其视为“第三方” cookie。

好处是涉及的代码很少,而且全部都使用了完善的东西...缺点是,如果要使用https,则需要对API主机进行一些控制/所有权-它们需要一个对于客户端域有效的证书,否则用户将收到证书警告-因此,如果所涉及的API不是您的或合作伙伴的API,那么它将不起作用(至少不适用于面向最终用户的事情)。