Storage Access API

浏览器、用户设置和存储分区对第三方 Cookie 的屏蔽,给依赖于嵌入式上下文中的 Cookie 和其他存储空间(例如用于身份验证的用户历程)的网站和服务带来了挑战。Storage Access API (SAA) 可让这些使用情形继续正常运行,同时尽可能限制跨网站跟踪。

实现状态

Browser Support

  • Chrome: 119.
  • Edge: 85.
  • Firefox: 65.
  • Safari: 11.1.

Source

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

标准化 API 之前,我们仍在努力解决所有剩余的阻塞问题

什么是 Storage Access API?

Storage Access API 是一种 JavaScript API,可让 iframe 在浏览器设置原本会拒绝访问存储空间的情况下请求存储空间访问权限。如果嵌入内容的使用场景依赖于加载跨网站资源,则可以使用该 API 根据需要向用户请求访问权限。

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

Storage Access API 允许提供特定的未分区 Cookie 和存储空间访问权限,同时最大限度地减少最终用户的负担,但仍会阻止通常用于用户跟踪的通用未分区 Cookie 和存储空间访问权限。

使用场景

某些第三方嵌入内容需要访问未分区的 Cookie 或存储空间,才能为用户提供更好的体验。但当第三方 Cookie 受到限制且存储空间分区功能处于启用状态时,这些嵌入内容将无法访问这些资源。

用例包括:

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

许多此类使用情形都涉及在嵌入式 iframe 中保留登录访问权限。

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

Storage Access API 是使用未分区 Cookie 和存储空间的替代方案之一,因此了解何时使用此 API 而不是其他 API 非常重要。它适用于以下使用情形:

  • 用户将与嵌入的内容互动,也就是说,它不是被动 iframe 或隐藏的 iframe。
  • 用户在顶级情境中访问过嵌入式来源,也就是说,该来源未嵌入到其他网站中。

有多种替代 API 可用于各种用例:

  • 具有独立分区状态的 Cookie (CHIPS) 可让开发者选择将 Cookie 存储在“分区”存储空间中,每个顶级网站都有一个单独的 Cookie Jar。例如,第三方网络聊天微件可能需要设置 Cookie 才能保存会话信息。会话信息是按网站保存的,因此在嵌入该 widget 的其他网站上,无需访问该 widget 设置的 Cookie。当嵌入式第三方 widget 需要跨不同来源共享相同的信息(例如登录会话详细信息或偏好设置)时,Storage Access API 非常有用。
  • 存储空间分区是一种让跨网站 iframe 使用现有 JavaScript 存储机制的方法,同时按网站划分底层存储空间。这样可防止一个网站中的嵌入式存储被其他网站上的同一嵌入内容访问。
  • Related Website Set (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.
    hasAccess = await document.hasStorageAccess();
    if (!hasAccess) {
      // Handle the lack of access (covered later)
    }
  }
  if (hasAccess) {
    // Use the cookies.
  }
}
handleCookieAccessInit();

Storage Access API 仅在 iframe 文档调用 requestStorageAccess(), 后才授予其存储空间访问权限,因此 hasStorageAccess() 最初可能会返回 false,例如,如果用户默认阻止第三方 Cookie,就会出现这种情况。 (不过,即使用户默认阻止第三方 Cookie,特定于网站的用户设置也可能允许在特定网站上访问 Cookie。) 使用此 API 进行的存储空间访问权限在 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);

权限提示

当用户首次点击该按钮时,浏览器提示通常会自动显示在地址栏中。以下屏幕截图显示了 Chrome 的提示示例,但其他浏览器也有类似的界面:

Chrome Storage Access API 权限提示。
Chrome 的 Storage Access API 权限提示

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

  • 如果网页和 iframe 在接受提示后的 30 天内被使用过。
  • 如果嵌入式 iframe 是相关网站集的一部分。
  • 如果 FedCM 用作存储空间访问的信任信号
  • 在 Firefox 中,对于已知网站(您曾以顶级网域身份与之互动过的网站),系统也会在前五次尝试中跳过提示。

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

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

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

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

将 FedCM 作为 SAA 的信任信号

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

当用户登录到某个依赖方 (RP) 时,如果该依赖方具有来自第三方身份提供方 (IdP) 的一些嵌入式内容,并且该依赖方使用 FedCM,则嵌入式 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 要求

您可以通过向 requestStorageAccess 调用传递 types 参数来请求访问未分区的本地存储空间。例如,如需请求访问未分区的本地存储空间,您可以调用 requestStorageAccess({localStorage: true})

如果启用了第三方 Cookie,此方法将授予访问权限,而无需用户激活或任何权限提示。如果用户已停用第三方 Cookie,则必须先提示用户,然后才能授予存储空间访问权限。

Storage Access API 的流程图,显示了如何获取非 Cookie 存储空间访问权限。
非 Cookie 存储访问请求流程。

首先,检查浏览器是否已获得存储空间访问权限:

async function hasCookieAccess(){
  // Check if Storage Access API is supported
  if (!document.requestStorageAccess) {
    // Storage Access API is not supported, so we assume it's an older browser that doesn't partition storage.
    throw new Error("requestStorageAccess is not supported")
  }

  // Check if access has already been granted or if the user has 3-party cookies enabled
  return document.hasStorageAccess();
}

如果已启用第三方 Cookie,请请求存储访问权限:

// Request storage access and return the storage handle
async function requestStorageHandle(){
// You can request for multiple types of non-cookie storage
// at once, or request for all of them with all:true
return document.requestStorageAccess({all:true});
}

如果第三方 Cookie 被屏蔽(例如,在无痕模式下),请检查查询权限,以确定是否需要显示用户提示。navigator.permissions.query({name: 'storage-access'}) 权限状态可以具有以下值:

  • granted。用户已授予访问权限。调用 requestStorageAccess 可获取非分区存储空间访问权限,而无需额外的用户提示。
  • prompt。用户尚未授予访问权限。设置点击监听器,并在用户互动后再次调用 requestStorageAccess
  • error。不支持此权限。如果支持 Storage Access API,则很可能也支持此权限。
// Returns `granted`, or `prompt`; or throws an error if storage-access
// permission is not supported
async function getStorageAccessPermission(){
  // Check the storage-access permission
  // Wrap this in a try/catch for browsers that support the
  // Storage Access API but not this permission check
    return navigator.permissions.query({name: 'storage-access'});
}

处理非 Cookie 分区存储的完整流程可以按如下方式实现:

async function getStorageHandle() {
    // Check if the user has 3-party cookie access
    if (await hasCookieAccess()) {
    // If the user has access, requestStorageAccess() will resolve automatically
        return requestStorageHandle();
    }

    // If the browser blocks third party cookies, check if the user has
    // accepted the prompt and granted access. If they have,
    // requestStorageAccess() will resolve automatically
    const permission = await getStorageAccessPermission();
    if (permission == 'granted') { // User has seen prompt and granted access
        return requestStorageHandle();
    }

    // Wait for user activation to prompt the user again
    // (or put your silent failure logic here instead)
    return new Promise((resolve, reject) => {
        document.querySelector('#myButton').addEventListener(e => {
            requestStorageHandle().then(resolve, reject);
        })
    })
}

// Use your storage
getStorageHandle().then(handle=>{
    handle.indexedDB.open(...);
}).catch(() => {
    // If the promise is rejected, you can use regular partitioned storage
    indexedDB.open(...);
})

使用存储访问标头进行后续加载

Storage Access Headers 是一种推荐的、性能更高的嵌入式内容(包括非 iframe 资源)加载方式。此功能自 Chrome 133 起提供。借助存储空间访问权限标头,浏览器可以识别用户是否已在当前情境中向第三方来源授予 storage-access 权限,并且可以在后续访问期间加载有权访问未分区 Cookie 的资源。

存储访问权限标头流程

使用 Storage Access 标头时,后续的网页访问会触发以下流程:

  1. 用户之前访问过嵌入 calendar.example 资源的 website.example,并通过 document.requestStorageAccess() 调用授予了 storage-access
  2. 用户再次访问嵌入了 calendar.example 资源的 website.example。此请求尚未获得对 Cookie 的访问权限,与之前一样。 不过,用户之前已授予 storage-access 权限,并且提取请求包含 Sec-Fetch-Storage-Access: inactive 标头,表明未分区的 Cookie 访问权限可用但未激活。
  3. calendar.example 服务器会使用 Activate-Storage-Access: retry; allowed-origin='<origin>' 标头(在本例中,<origin>https://website.example)进行响应,以表明资源提取需要使用具有 storage-access 权限的未分区 Cookie。
  4. 浏览器会重试该请求,这次会包含未分区的 Cookie(为此次提取和后续提取激活 storage-access 权限)。
  5. calendar.example 服务器会返回个性化的 iframe 内容。 响应包含一个 Activate-Storage-Access: load 标头,用于指示浏览器应在启用 storage-access 权限的情况下加载内容(换句话说,以未分区的 Cookie 访问权限加载,就像已调用 document.requestStorageAccess() 一样)。
  6. 用户代理使用 storage-access 权限加载具有未分区 Cookie 访问权限的 iframe 内容。完成此步骤后,该 widget 即可按预期运行。
一张流程图,用于说明存储访问标头流程。
存储访问标头流程图。

使用存储访问权限标头

下表列出了存储空间访问权限标头。

流程 标题 说明
请求 Sec-Fetch-Storage-Access
注意:浏览器会在包含凭据(例如 new Request('request.example', { credentials: 'include' });)的跨网站请求中自动发送此标头。
none 嵌入式应用没有存储空间访问权限。
inactive 嵌入内容具有相应权限,但未使用该权限。
请求还必须包含 Origin 标头。
active 嵌入内容具有未分区 Cookie 访问权限。
响应 Activate-Storage-Access load 指示浏览器向嵌入者授予对所请求资源的未分区 Cookie 的访问权限。
如果已授予 storage-access 权限,则包含此标头等同于调用 document.requestStorageAccess()。这意味着,系统不会向用户显示任何其他提示。
retry 指示浏览器激活存储访问权限,然后重试请求。
allowed-origin <origin> 指定允许哪个来源发起需要凭据的请求(例如,https://site.example*)。

例如,可以使用存储访问权限标头从第三方加载图片:

  // On the client side
  <img src="https://server.example/image">

在这种情况下,server.example 应在服务器端实现以下逻辑:

  app.get('/image', (req, res) => {
  const storageAccessHeader = req.headers['sec-fetch-storage-access'];

  if (storageAccessHeader === 'inactive') {
    // The user needs to grant permission, trigger a prompt

    // Check if the requesting origin is allowed
    // to send credentialed requests to this server.
    // Assuming the `validate_origin(origin)` method is previously defined:
    if (!validate_origin(req.headers.origin)) {
      res.status(401).send(req.headers.origin +
        ' is not allowed to send credentialed requests to this server.');
      return;
    }
    // 'retry' header value indicates that the content load request should be re-sent after the user has granted permissions
    res.set('Activate-Storage-Access', `retry; allowed-origin='${req.headers.origin}'`);
    res.status(401).send('This resource requires storage access. Please grant permission.');
  } else if (storageAccessHeader === 'active') {
    // User has granted permission, proceed with access
    res.set('Activate-Storage-Access', 'load');
    // Include the actual iframe content here
    res.send('This is the content that requires cookie access.');
  } else {
    // Handle other cases (e.g., 'Sec-Fetch-Storage-Access': 'none')
  }
});

存储空间访问 API 演示使用存储空间访问标头嵌入第三方内容(包括非 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 时,需要以下沙盒权限:

  • 需要 allow-storage-access-by-user-activation 才能允许访问 Storage Access API。
  • 需要 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,必须使用以下两个属性设置这些 Cookie:

  • SameSite=None - 这是将 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() 方法

Browser Support

  • Chrome: 119.
  • Edge: 119.
  • Firefox: not supported.
  • Safari: not supported.

Source

requestStorageAccessFor() 方法的工作方式与 requestStorageAccess() 类似,但适用于顶级资源。它只能用于相关网站集中的网站,以防止授予对第三方 Cookie 的一般访问权限。

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

top-level-storage-access 权限查询

Browser Support

  • Chrome: 113.
  • Edge: 113.
  • Firefox: not supported.
  • Safari: not supported.

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

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

将 Related Website Set 与 Storage Access API 搭配使用时,可获得某些额外的功能,如下表所示:

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

演示:设置和访问 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 标志。

资源