Storage Access API

Chrome 将逐步停止对第三方 Cookie 的支持分区存储,以减少跨网站跟踪。对于在身份验证等用户体验历程中依赖 Cookie 和其他嵌入式环境存储方法的网站和服务而言,这带来了一项挑战。Storage Access API (SAA) 可让这些用例继续运行,同时尽可能地限制跨网站跟踪。

实现状态

浏览器支持

  • 119
  • 85
  • 65
  • 11.1

来源

Storage Access API 可在所有主流浏览器中使用,但在不同浏览器之间存在细微的实现差异。在本文的相关部分中,我们着重介绍了这些不同之处。

对 API 进行标准化之前,我们会继续解决其余所有阻止问题

什么是 Storage Access API?

Storage Access API 是一种 JavaScript API,可让 iframe 在浏览器设置拒绝访问时请求存储空间访问权限。如果嵌入的用例依赖于加载跨网站资源,可根据需要使用该 API 向用户请求访问权限。

如果存储请求被批准,则 iframe 将可以访问其未分区 Cookie 和存储空间,当用户将 iframe 作为顶级网站访问时,这些 Cookie 和存储空间也会获得访问权限。

Storage Access API 允许向最终用户提供特定的未分区 Cookie 和存储访问,同时仍可尽量减少通常用于用户跟踪的常规未分区 Cookie 和存储访问。

用例

为了向用户提供更好的体验,有些第三方嵌入代码需要访问未分区 Cookie 或存储空间,而第三方 Cookie 被弃用并启用存储空间分区后,您将无法使用相应服务。

用例包括:

  • 需要登录会话详细信息的嵌入式评论微件。
  • 需要登录会话详细信息的社交媒体“赞”按钮。
  • 需要登录会话详细信息的嵌入式文档。
  • 为嵌入视频提供的高级体验(例如,不向已登录用户展示广告、了解用户偏好的字幕或限制某些视频类型)。
  • 嵌入式支付系统。

其中的许多用例都涉及在嵌入式 iframe 中持久保留登录访问权限。

何时使用 Storage Access API 而不是其他 API

Storage Access API 是使用未分区 Cookie 和存储空间的替代方案之一,因此请务必了解何时使用此 API 与其他 API 相比。它适用于同时满足以下两个条件的用例:

  • 用户将与嵌入的内容互动,也就是说,它不是被动 iframe 或隐藏 iframe。
  • 用户已在顶级上下文中(即该来源未嵌入到其他网站)访问了嵌入的来源。

还有适用于各种用例的替代 API:

  • 借助 Cookie 具有独立分区状态 (CHIPS),开发者可以选择将 Cookie 保存到“分区”存储空间中,并为每个顶级网站提供一个单独的 Cookie jar。例如,第三方网络聊天微件可能依赖于设置 Cookie 来保存会话信息。会话信息是按网站保存的,因此无需在嵌入了该 Cookie 的其他网站上访问该 Cookie 设置的 Cookie。如果嵌入式第三方 widget 依赖于跨不同源共享相同的信息(例如已登录的会话详细信息或偏好设置),则 Storage Access API 非常有用。
  • 通过存储空间分区,跨网站 iframe 便可以使用现有的 JavaScript 存储机制,同时按网站划分底层存储空间。这样可防止一个网站中嵌入的存储空间通过其他网站中的相同嵌入位置访问。
  • Related Websites Set (RWS) 可供组织声明网站之间的关系,以便浏览器出于特定目的允许有限的未分区 Cookie 和存储空间访问权限。网站仍然需要使用 Storage Access API 请求访问权限,但对于该组内的网站,无需用户提示即可授予访问权限。
  • Federated Credential Management (FedCM) 是一种可保护隐私的方法,用于联合身份服务。Storage Access API 用于处理登录后访问未分区 Cookie 和存储空间的情况。对于某些用例,FedCM 提供了 Storage Access API 的替代解决方案,由于该 API 具有更侧重于登录的浏览器提示,因此可能更合适。但是,采用 FedCM 通常需要对代码进行额外的更改,例如支持其 HTTP 端点。
  • 反欺诈广告相关衡量 API 也存在,但 Storage Access API 并非旨在解决此类问题。

使用 Storage Access API

Storage Access API 有两个基于 promise 的方法:

此外,它还与 Permissions API 集成。这样,您就可以在第三方上下文中查看存储空间访问权限的状态,该状态会指示是否会自动授予对 document.requestStorageAccess() 的调用:

使用 hasStorageAccess() 方法

网站首次加载时,可以使用 hasStorageAccess() 方法检查是否已授予第三方 Cookie 访问权限。

// Set a hasAccess boolean variable which defaults to false.
let hasAccess = false;

async function handleCookieAccessInit() {
  if (!document.hasStorageAccess) {
    // Storage Access API is not supported so best we can do is
    // hope it's an older browser that doesn't block 3P cookies.
    hasAccess = true;
  } else {
    // Check whether access has been granted via the Storage Access API.
    // Note on page load this will always be false initially so we could be
    // skipped in this example, but including for completeness for when this
    // is not so obvious.
    hasAccess = await document.hasStorageAccess();
    if (!hasAccess) {
      // Handle the lack of access (covered later)
    }
  }
  if (hasAccess) {
    // Use the cookies.
  }
}
handleCookieAccessInit();

只有在 iframe 文档调用 requestStorageAccess(), 后,系统才会向该文档授予存储访问权限,因此 hasStorageAccess() 最初将始终返回 false,除非同一 iframe 中的另一个同源文档已被授予访问权限。该授权会保留在 iframe 内的同源导航中,专门用于在为要求在 HTML 文档初始请求中存在 Cookie 的网页授予访问权限后,能够进行重新加载。

使用 requestStorageAccess() 方法

如果 iframe 没有访问权限,则可能需要使用 requestStorageAccess() 方法请求访问权限:

if (!hasAccess) {
  try {
    await document.requestStorageAccess();
  } catch (err) {
    // Access was not granted and it may be gated behind an interaction
    return;
  }
}

首次请求此请求时,用户可能需要通过浏览器提示批准此访问,之后 promise 将进行解析;如果使用 await,则拒绝该访问并导致异常。

为了防止滥用行为,此浏览器提示只会在用户互动发生后显示。因此,requestStorageAccess() 一开始需要从用户激活的事件处理脚本调用,而不是在加载 iframe 时立即调用:

async function doClick() {

  // Only do this extra check if access hasn't already been given
  // based on the hasAccess variable.
  if (!hasAccess) {
    try {
      await document.requestStorageAccess();
      hasAccess = true; // Can assume this was true if above did not reject.
    } catch (err) {
      // Access was not granted.
      return;
    }
  }

  if (hasAccess) {
    // Use the cookies
  }
}

document.querySelector('#my-button').addEventListener('click', doClick);

如果您需要使用本地存储而不是 Cookie,可以执行以下操作:

let handle = null;

async function doClick() {
  if (!handle) {
    try {
      handle = await document.requestStorageAccess({localStorage: true});
    } catch (err) {
      // Access was not granted.
      return;
    }
  }

  // Use handle to access unpartitioned local storage.
  handle.localStorage.setItem('foo', 'bar');
}

document.querySelector('#my-button').addEventListener('click', doClick);

权限提示

用户首次点击该按钮时,浏览器提示通常会显示在地址栏中。以下是 Chrome 的提示示例,但其他浏览器的界面类似:

Chrome Storage Access API 权限提示的屏幕截图
Chrome 的 Storage Access API 权限提示

在某些情况下,浏览器可能会跳过提示,并自动提供权限:

  • 在接受提示后,该网页和 iframe 是否在过去 30 天内使用过。
  • 如果嵌入的 iframe 是 Related Website Set 的一部分。
  • 在 Firefox 中,前 5 次尝试也会对已知网站(您在顶级网站互动过的网站)跳过此提示。

或者,在某些情况下,系统可能会自动拒绝此方法而不显示提示:

  • 如果用户之前没有访问拥有 iframe 的顶级文档(而不是在 iframe 中)的网站,并且与之进行过互动。这意味着 Storage Access API 仅适用于用户之前在第一方环境中访问过的嵌入式网站。
  • 如果在互动发生后没有事先批准提示,则在用户互动事件之外调用 requestStorageAccess() 方法。

虽然用户在初次使用时会收到提示,但后续访问可以在不提示用户的情况下,也无需用户在 Chrome 和 Firefox 中互动即可解析 requestStorageAccess()。请注意,Safari 始终需要用户互动。

由于 Cookie 和存储空间访问权限可能会在无提示或用户互动的情况下被授予,因此通常可以通过在页面加载时调用 requestStorageAccess(),在用户与支持此功能的浏览器(Chrome 和 Firefox)上互动之前,获取未分区 Cookie 或存储空间访问权限。这样,您便可以立即访问未分区 Cookie 和存储空间,从而提供更完整的体验,即使用户未与 iframe 互动之前也是如此。相较于等待用户互动,在某些情况下,这样可提供更好的用户体验。

使用 storage-access 权限查询

如需检查能否在无需用户互动的情况下授予访问权限,您可以检查 storage-access 权限的状态,仅在不需要用户操作时提前调用 requestStoreAccess(),而不是在需要互动时调用并失败。

这也让您可以通过显示不同的内容(例如登录按钮)预先满足提示的需要。

以下代码将 storage-access 权限检查添加到前面的示例中:

// Set a hasAccess boolean variable which defaults to false except for
// browsers which don't support the API - where we assume
// such browsers also don't block third-party cookies.
let hasAccess = false;

async function hasCookieAccess() {
  // Check if Storage Access API is supported
  if (!document.requestStorageAccess) {
    // Storage Access API is not supported so best we can do is
    // hope it's an older browser that doesn't block 3P cookies.
    return true;
  }

  // Check if access has already been granted
  if (await document.hasStorageAccess()) {
    return true;
  }

  // Check the storage-access permission
  // Wrap this in a try/catch for browsers that support the
  // Storage Access API but not this permission check
  // (e.g. Safari and older versions of Firefox).
  let permission;
  try {
    permission = await navigator.permissions.query(
      {name: 'storage-access'}
    );
  } catch (error) {
    // storage-access permission not supported. Assume no cookie access.
    return false;
  }

    if (permission) {
    if (permission.state === 'granted') {
      // Permission has previously been granted so can just call
      // requestStorageAccess() without a user interaction and
      // it will resolve automatically.
      try {
        await document.requestStorageAccess();
        return true;
      } catch (error) {
        // This shouldn't really fail if access is granted, but return false
        // if it does.
        return false;
      }
    } else if (permission.state === 'prompt') {
      // Need to call requestStorageAccess() after a user interaction
      // (potentially with a prompt). Can't do anything further here,
      // so handle this in the click handler.
      return false;
          } else if (permission.state === 'denied') {
            // Currently not used. See:
      // https://github.com/privacycg/storage-access/issues/149
      return false;
          }
    }

  // By default return false, though should really be caught by one of above.
  return false;
}

async function handleCookieAccessInit() {
  hasAccess = await hasCookieAccess();

  if (hasAccess) {
    // Use the cookies.
  }
}

handleCookieAccessInit();

沙盒化 iframe

沙盒化 iframe 中使用 Storage Access API 时,需要以下沙盒权限:

  • 如需允许访问 Storage Access API,则必须提供 allow-storage-access-by-user-activation
  • 必须启用 allow-scripts 才能允许使用 JavaScript 调用 API。
  • 必须启用 allow-same-origin 才能允许访问同源 Cookie 和其他存储空间。

例如:

<iframe sandbox="allow-storage-access-by-user-activation
                 allow-scripts
                 allow-same-origin"
        src="..."></iframe>

若要在 Chrome 中使用 Storage Access API 进行访问,跨网站 Cookie 必须使用以下两个属性设置:

  • SameSite=None - 将 Cookie 标记为跨网站时需要使用此属性
  • Secure - 用于确保只能访问 HTTPS 网站设置的 Cookie。

在 Firefox 和 Safari 中,Cookie 默认为 SameSite=None,且不会将 SSA 限制为 Secure Cookie,因此这些属性不是必需属性。建议明确说明 SameSite 属性并始终使用 Secure Cookie。

顶级页面访问权限

Storage Access API 旨在实现对嵌入式 iframe 中的第三方 Cookie 的访问。

还有一些其他用例,顶级页面需要访问第三方 Cookie。例如,受 Cookie 限制的图片或脚本,网站所有者可能希望将其直接添加到顶级文档,而不是 iframe 中。为解决这一使用情形,Chrome 提议了 Storage Access API 的扩展程序,用于添加 requestStorageAccessFor() 方法。

requestStorageAccessFor() 方法

浏览器支持

  • 119
  • 119
  • x
  • x

来源

requestStorageAccessFor() 方法的运作方式与 requestStorageAccess() 类似,但适用于顶级资源。它只能用于 Related Website Set 中的网站,以防止授予对第三方 Cookie 的常规访问权限。

如需详细了解如何使用 requestStorageAccessFor(),请参阅 Related Website Sets:开发者指南

top-level-storage-access 权限查询

浏览器支持

  • x
  • x
  • x
  • x

storage-access 权限类似,还有一个 top-level-storage-access 权限,用于检查是否可以为 requestStorageAccessFor() 授予访问权限。

与 RWS 搭配使用时,Storage Access API 有何不同?

将 Related Website Set 与 Storage Access API 搭配使用时,还可以使用下表中详细介绍的一些附加功能:

不使用 RWS 支持 RWS
需要通过用户手势来发起存储空间访问权限请求
用户必须先在顶级上下文中访问所请求的存储来源,然后才能授予访问权限
可以跳过首次用户提示
如果之前已授予访问权限,则无需调用 requestStorageAccess
在相关网站网站中自动授予对其他网域的访问权限
支持访问顶级页面requestStorageAccessFor
使用 Storage Access API 而不使用 Related Website Set 和使用 Related Website Set 之间的差异

演示:设置和访问 Cookie

以下演示展示了如何在演示第二个网站的嵌入式框架中访问您在演示第一个屏幕中设置的 Cookie:

storage-access-api-demo.glitch.me

此演示需要停用第三方 Cookie 的浏览器:

  • Chrome 118 或更高版本(设置了 chrome://flags/#test-third-party-cookie-phaseout 标志并重启了浏览器)。
  • Firefox
  • Safari

演示:设置本地存储

以下演示介绍了如何使用 Storage Access API 从第三方 iframe 访问未分区的广播频道:

https://saa-beyond-cookies.glitch.me/

此演示版要求使用 Chrome 125 或更高版本,并启用 test-third-party-cookie-phaseout 标志。

资源