reactjs - 前后端分离 : Safari not storing cookies from API which is hosted on a separate domain than its Frontend SPA client

标签 reactjs rest authentication cookies safari

据我所知,我有一个现在相当常见的设置:一个位于其自己域(例如 myapi.com)上的后端 REST API,以及一个单页前端应用程序这是在其他地方提供的,例如 myapp.com

SPA 是 API 的客户端,API 要求用户在执行操作之前进行身份验证。

后端 API 使用 cookie 来存储某些允许的来源(其中 myapp.com)的 session 数据。这是为了有一个安全的总线来传输和存储身份验证数据,而不必担心客户端。

在 Chrome、Opera 和 Firefox 中,这工作得很好:进行 API 调用来验证用户身份,返回 Cookie 并将其存储在浏览器中,以便与下一次调用一起推送。

另一方面,Safari 确实收到了 cookie,但拒绝存储它们:

Auth call

First call

我怀疑 Safari 将 API 域视为第 3 方 cookie 域,因此阻止存储 cookie。

这是 Safari 中的预期行为吗?如果是这样,有哪些最佳实践可以解决这个问题?

最佳答案

延续 answering your own question 的传统关于这一点。

TL;DR 这是 Safari 中所需的行为。解决这个问题的唯一方法是将用户带到 API 域(问题中的 myapi.com)上托管的网页,并从那里设置一个 cookie - 任何东西,你都可以编写一个小代码如果你愿意的话,可以在 cookies 里放一首诗。

完成此操作后,该域将被列入“白名单”,Safari 会善待您,并在任何后续调用中设置您的 Cookie,即使来自不同域的客户端也是如此。

这意味着您可以保持身份验证逻辑不变,只需引入一个为您设置“种子”cookie 的哑端点。在我的 Ruby 应用程序中,如下所示:

class ServiceController < ActionController::Base
  def seed_cookie
    cookies[:s] = {value: 42, expires: 1.week, httponly: true} # value can be anything at all
    render plain: "Checking your browser"
  end
end

客户端,您可能需要检查if the browser making the request is Safari并在打开那个丑陋的弹出窗口后推迟您的登录逻辑:

const doLogin = () => {
  if(/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
    const seedCookie = window.open(`http://myapi.com/seed_cookie`, "s", "width=1, height=1, bottom=0, left=0, toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no")
    setTimeout(() => {
      seedCookie.close();
      // your login logic;
    }, 500);
  } else {
    // your login logic;
  }
}

更新:上面的解决方案对于登录用户来说效果很好,即它正确地将当前浏览器 session 的 API 域“列入白名单”。

但不幸的是,用户刷新页面似乎会使浏览器重置为原始状态,其中 API 域的第 3 方 cookie 被阻止。

我发现处理窗口刷新情况的一个好方法是 detect it in javascript页面加载后,将用户重定向到与上面的 API 端点执行相同操作的 API 端点,然后将用户重定向到他们导航到的原始 URL(正在刷新的页面):

if(performance.navigation.type == 1 && /^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
  window.location.replace(`http://myapi.com/redirect_me`);
}

让事情变得复杂的是,如果响应的 HTTP 状态是 30X(重定向),Safari 将不会存储 cookie。因此,Safari 友好的解决方案涉及设置 cookie 并返回 200 响应以及将处理浏览器内重定向的 JS 片段。

就我而言,作为 Rails 应用程序的后端,该端点如下所示:

def redirect_me
  cookies[:s] = {value: 42, expires: 1.week, httponly: true}
  render body: "<html><head><script>window.location.replace('#{request.referer}');</script></head></html>", status: 200, content_type: 'text/html'
end

关于reactjs - 前后端分离 : Safari not storing cookies from API which is hosted on a separate domain than its Frontend SPA client,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52446840/

相关文章:

javascript - Object.assign (REACT) 输出不正确且未按预期工作

java - 用于在休息端点上传文件的正确 Http 状态代码

web-services - 基于 REST 的 Web 服务中的 SOAP 消息

javascript - React Js - 以动态方式渲染 Lightbox 组件的问题

reactjs - 为什么我无法在 React Native 的 Animated.event() 中触发 nativeEvent 回调?

java - 即使 SQLite 数据库中存在数据也无法登录

ruby-on-rails - 无法远程验证 mongodb

java - 后处理拦截器 : How to access HTTP request?

javascript - react 函数不渲染任何东西

java - 为 JAX-RS 2.0 客户端 API 设置请求超时