Storage Access API

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

实现状态

浏览器支持

  • Chrome:119.
  • Edge: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 Jar。例如,第三方网络聊天 widget 可能需要设置 Cookie 才能保存会话信息。会话信息会按网站保存,因此无需在嵌入该微件的其他网站上访问该微件设置的 Cookie。当嵌入的第三方 widget 依赖于在不同来源之间共享相同信息时(例如已登录的会话详情或偏好设置),Storage Access API 非常有用。
  • 存储分区是一种跨网站 iframe 使用现有 JavaScript 存储机制并按网站划分底层存储空间的方式。这样可以防止一个网站中的嵌入式存储空间被其他网站上的相同嵌入内容访问。
  • 相关网站集 (RWS) 是一种组织声明网站之间关系的方式,以便浏览器允许出于特定目的进行有限的分区 Cookie 和存储访问。网站仍需要使用 Storage Access API 请求访问权限,但对于该集合中的网站,无需用户提示即可授予访问权限。
  • Federated Credential Management (FedCM) 是一种可保护隐私的联合身份服务方法。Storage Access API 用于处理在登录后访问未分区的 Cookie 和存储空间。对于某些用例,FedCM 可作为 Storage Access 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 权限提示

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

  • 如果在接受提示后的过去 30 天内使用过相应网页和 iframe。
  • 如果嵌入的 iframe 属于相关网站集
  • 如果 FedCM 用作存储空间访问的信任信号
  • 在 Firefox 中,对于已知网站(您在顶级页面上与之互动过的网站),系统在前五次尝试时也会跳过提示。

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

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

虽然系统会在用户首次使用时提示,但在 Chrome 和 Firefox 中,后续访问时无需提示即可解析 requestStorageAccess(),也不需要用户互动。请注意,Safari 始终需要用户互动。

由于系统可能会在不提示或用户互动的情况下授予 Cookie 和存储空间访问权限,因此在支持此功能的浏览器(Chrome 和 Firefox)中,通常可以在用户互动之前通过在网页加载时调用 requestStorageAccess() 来获取未分区的 Cookie 或存储空间访问权限。这样,您或许可以立即访问未分区的 Cookie 和存储空间,并在用户与 iframe 互动之前提供更完整的体验。在某些情况下,这比等待用户互动能提供更好的用户体验。

将 FedCM 用作 SAA 的信任信号

FedCM(联合身份管理)是一种可保护隐私的联合身份服务(例如“使用...登录”)方法,不依赖于第三方 Cookie 或导航重定向。

当用户登录包含来自第三方身份提供商 (IdP) 的某些嵌入内容且具有 FedCM 的依赖方 (RP) 时,嵌入的 IdP 内容可以自动获得对其自己的顶级未分区 Cookie 的存储访问权限。若要使用 FedCM 启用自动存储空间访问权限,必须满足以下条件:

  • FedCM 身份验证(用户登录状态)必须处于有效状态。
  • RP 已通过设置 identity-credentials-get 权限来选择启用此功能,例如:
<iframe src="https://idp.example" allow="identity-credentials-get"></iframe>

例如,idp.example iframe 嵌入在 rp.example 中。当用户使用 FedCM 登录时,idp.example iframe 可以请求对其自己的顶级 Cookie 的存储访问权限。

rp.example 会发出 FedCM 调用,以便使用身份提供方 idp.example 登录用户:

// The user will be asked to grant FedCM permission.
const cred = await navigator.credentials.get({
  identity: {
    providers: [{
      configURL: 'https://idp.example/fedcm.json',
      clientId: '123',
    }],
  },
});

用户登录后,IdP 可以从 idp.example iframe 中调用 requestStorageAccess(),前提是 RP 已通过权限政策明确允许这样做。嵌入内容将自动获得对其自身顶级 Cookie 的存储权限,而无需用户激活或再次显示权限提示

// Make this call within the embedded IdP iframe:

// No user gesture is needed, and the storage access will be auto-granted.
await document.requestStorageAccess();

// This returns `true`.
const hasAccess = await document.hasStorageAccess();

只有当用户使用 FedCM 登录时,系统才会自动授予此权限。身份验证处于非活动状态后,授予存储空间访问权限时将适用标准的 SAA 要求

使用 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 时,需要具有以下沙盒权限:

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

例如:

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

若要在 Chrome 中使用 Storage Access API 访问跨网站 Cookie,必须使用以下两个属性设置跨网站 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() 类似,但适用于顶级资源。此功能只能用于相关网站集中的网站,以防止向第三方 Cookie 授予一般访问权限。

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

top-level-storage-access 权限查询

浏览器支持

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

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

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

将相关网站集与 Storage Access API 搭配使用时,您可以使用下表中详述的某些额外功能:

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

演示:设置和访问 Cookie

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

storage-access-api-demo.glitch.me

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

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

演示:设置本地存储

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

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

此演示需要 Chrome 125 或更高版本,且已启用 test-third-party-cookie-phaseout 标志。

资源