Storage Access API

浏览器、用户设置和存储分区会阻止第三方 Cookie,这对依赖于嵌入式情境中的 Cookie 和其他存储空间的网站和服务来说,是一次考验,因为这些网站和服务需要 Cookie 和其他存储空间来实现身份验证等用户体验历程。Storage Access API (SAA) 可让这些用例继续正常运行,同时尽可能限制跨网站跟踪。

实现状态

浏览器支持

  • Chrome:119。
  • 边缘:85。
  • Firefox:65。
  • Safari: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 罐。例如,第三方网络聊天微件可能依赖于设置 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 using 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 requestStorageAccess() 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 中,对于已知网站(您在顶级页面上与之互动过的网站),系统在前五次尝试时也会跳过提示。

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

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

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

由于系统可能会在不提示或用户互动的情况下授予 Cookie 和存储空间访问权限,因此在支持此功能的浏览器(Chrome 和 Firefox)中,通常可以在用户互动之前通过在网页加载时调用 requestStorageAccess() 来获取未分区的 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 earlier 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') {
            // Not used: see https://github.com/privacycg/storage-access/issues/149
      return false;
          }
    }

  // By default return false, though should really be caught by earlier tests.
  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 标记为跨网站 Cookie
  • Secure - 可确保仅访问由 HTTPS 网站设置的 Cookie。

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

顶级页面访问权限

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

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

requestStorageAccessFor() 方法

浏览器支持

  • Chrome:119。
  • Edge:119。
  • Firefox:不支持。
  • Safari:不支持。

来源

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

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

top-level-storage-access 权限查询

浏览器支持

  • Chrome:不支持。
  • Edge:不支持。
  • Firefox:不受支持。
  • Safari:不受支持。

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 标志。

资源