FedCM 更新:针对 Continuation API 软件包和 Storage Access API 自动授权的源试用

从 Chrome 126 开始,开发者可以开始针对一组桌面版 Federated Credential Management API (FedCM) 功能运行源试用,这些功能支持某些授权用例。该软件包由 Continuation API 和 Parameters API 组成,可提供类似于 OAuth 授权流程的体验,其中涉及身份提供程序 (IdP) 提供的权限对话框。该软件包还包含其他更改,例如 Fields API、多个 config网址 和自定义账号标签。从 Chrome 126 开始,我们还将推出 Storage Access API (SAA) 的源代码试用版,如果用户过去曾成功使用 FedCM 登录,则会自动授予 SAA 请求。

源试用:FedCM 接续 API 软件包

FedCM 接续 API 软件包由多个 FedCM 扩展组成:

Continuation API

用户正在登录 RP,然后通过按钮模式授权。

您可以查看 Glitch 上的 API 演示

借助 Continuation API,IdP 的身份断言端点可以选择返回 FedCM 将呈现的网址,以允许用户继续执行多步登录流程。这样一来,IdP 就可以请求用户授予依赖方 (RP) 在现有 FedCM 界面中无法提供的权限,例如对用户的服务器端资源的访问权限。

通常,ID 断言端点会返回进行身份验证所需的令牌。

{
  "token": "***********"
}

不过,使用 Continuation API 时,身份断言端点可以返回一个 continue_on 属性,其中包含指向身份断言端点的绝对路径或相对路径。

{
  // In the id_assertion_endpoint, instead of returning a typical
  // "token" response, the IdP decides that it needs the user to
  // continue on a pop-up window:
  "continue_on": "/oauth/authorize?scope=..."
}

浏览器收到 continue_on 响应后,系统会立即打开一个新的弹出式窗口,并将用户定向到指定路径。

用户与网页互动(例如,授予进一步的权限以与 RP 共享额外信息)后,IdP 网页可以调用 IdentityProvider.resolve() 来解析原始 navigator.credentials.get() 调用,并将令牌作为参数返回。

document.getElementById('allow_btn').addEventListener('click', async () => {
  let accessToken = await fetch('/generate_access_token.cgi');
  // Closes the window and resolves the promise (that is still hanging
  // in the relying party's renderer) with the value that is passed.
  IdentityProvider.resolve(accessToken);
});

然后,浏览器会自行关闭弹出式窗口,并将令牌返回给 API 调用方。

如果用户拒绝请求,您可以通过调用 IdentityProvider.close() 关闭窗口。

IdentityProvider.close();

如果用户出于某种原因更改了弹出式窗口中的账号(例如,IdP 提供了“切换用户”功能,或者在委托情况下),resolve 调用会接受一个可选的第二个参数,允许执行以下操作:

IdentityProvider.resolve(token, {accountId: '1234');

Parameters API

借助 Parameters API,RP 可以向ID 断言端点提供其他参数。借助 Parameters API,RP 可以向 IdP 传递其他参数,以请求基本登录以外的资源权限。用户将通过通过 Continuation API 启动的 IdP 控制的用户体验流程授予这些权限。

如需使用此 API,请在 navigator.credentials.get() 调用中将参数作为对象添加到 params 属性。

let {token} = await navigator.credentials.get({
  identity: {
    providers: [{
      clientId: '1234',
      configURL: 'https://idp.example/fedcm.json',
      // Key/value pairs that need to be passed from the
      // RP to the IdP but that don't really play any role with
      // the browser.
      params: {
        IDP_SPECIFIC_PARAM: '1',
        foo: 'BAR',
        ETC: 'MOAR',
        scope: 'calendar.readonly photos.write',
      }
    },
  }
});

params 对象中的属性名称会附加 param_。在上面的示例中,params 属性包含 IDP_SPECIFIC_PARAM(作为 '1')、foo(作为 'BAR')、ETC(作为 'MOAR')和 scope(作为 'calendar.readonly photos.write')。这将在请求的 HTTP 正文中转换为 param_IDP_SPECIFIC_PARAM=1&param_foo=BAR&param_ETC=MOAR&param_scope=calendar.readonly%20photos.write

POST /fedcm_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=false&param_IDP_SPECIFIC_PARAM=1&param_foo=BAR&param_ETC=MOAR&param_scope=calendar.readonly%20photos.write

动态获取权限

一般而言,在需要时请求权限对用户最有帮助,而不是在开发者认为最容易实现权限时请求权限。例如,最好在用户准备拍照时请求访问相机权限,而不是在用户访问网站后立即请求权限。同样的做法也适用于服务器资源。仅在用户需要时请求权限。这称为“动态授权”。

如需使用 FedCM 动态请求授权,IdP 可以:

  1. 使用 IdP 可以理解的必需参数(例如 scope)调用 navigator.credentials.get()
  2. ID 断言端点用于确认用户已登录,并以 continue_on 网址作为响应。
  3. 浏览器会打开一个弹出式窗口,其中包含 IdP 的权限页面,请求用户授予与请求的范围匹配的其他权限。
  4. 通过 IdP 通过 IdentityProvider.resolve() 授权后,该窗口会关闭,并且 RP 的原始 navigator.credentials.get() 调用会获取相关令牌或授权代码,以便 RP 可以将其换取适当的访问令牌。

Fields API

借助 Fields API,RP 可以声明要从 IdP 请求的账号属性,以便浏览器在 FedCM 对话框中呈现适当的披露界面;IdP 有责任在返回的令牌中包含请求的字段。不妨考虑在 OpenID Connect 中请求“基本个人资料”,而不是在 OAuth 中请求“范围”。

微件模式下的披露消息。
微件模式下的披露信息。
按钮模式下的披露消息。
按钮模式下的披露声明。

如需使用 Fields API,请在 navigator.credentials.get() 调用中将参数作为数组添加到 fields 属性。这些字段目前可以包含 'name''email''picture',但未来可能会扩展为包含更多值。

包含 fields 的请求如下所示:

let { token } = await navigator.credentials.get({
  identity: {
    providers: [{
      fields: ['name', 'email', 'picture'],
      clientId: '1234',
      configURL: 'https://idp.example/fedcm.json',
      params: {
        scope: 'drive.readonly calendar.readonly',
      }
    },
  }
  mediation: 'optional',
});

发送到ID 断言端点的 HTTP 请求包含 RP 指定的 fields 参数(如果这不是回访用户,则将 disclosure_text_shown 参数设置为 true),以及浏览器在 disclosure_shown_for 参数中向用户披露的字段:

POST /id_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=true&fields=email,name,picture&disclosure_shown_for=email,name,picture

如果 RP 需要访问 IdP 中的任何其他数据(例如访问日历),则应使用上述自定义参数进行处理。IdP 会返回 continue_on 网址以请求权限。

如果 fields 为空数组,请求将如下所示:

let { token } = await navigator.credentials.get({
  identity: {
    providers: [{
      fields: [],
      clientId: '1234',
      configURL: 'https://idp.example/fedcm.json',
      params: {
        scope: 'drive.readonly calendar.readonly',
      }
    },
  }
  mediation: 'optional',
});

如果 fields 是空数组,用户代理将跳过披露界面。

披露声明不会显示在微件模式下。在按钮流程中,系统会完全跳过披露声明界面。
披露信息不会在 widget 模式下显示。在按钮流程中,系统会完全跳过披露声明界面。

即使 accounts 端点的响应不包含与 approved_clients 中的 RP 匹配的客户端 ID,也存在这种情况。

在这种情况下,发送到ID 断言端点disclosure_text_shown 在 HTTP 正文中为 false:

POST /id_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=false

多个 config网址

借助多个 config网址,IdP 可以通过在众所周知的文件中指定与配置文件相同的 accounts_endpointlogin_url 来容纳 IdP 的多个配置文件。

如果将 accounts_endpointlogin_url 添加到 well-known 文件,系统会忽略 provider_urls,以便 IdP 支持多个配置文件。如果没有,provider_urls 将继续生效,以便向后兼容。

支持多个 config网址 的 well-known 文件可能如下所示:

{
  "provider_urls": [ "https://idp.example/fedcm.json" ],
  "accounts_endpoint": "https://idp.example/accounts",
  "login_url": "https://idp.example/login"
}

这样一来,我们就可以:

  1. 与现有的众所周知的文件以及已在实际环境中部署的较低版本的浏览器保持向后和向前兼容性。
  2. 可以有任意数量的配置文件,只要它们都指向相同的 accounts_endpointlogin_url 即可。
  3. 无法向向 accounts_endpoint 发出的凭据提取请求中添加熵,因为熵必须在“.well-known”级别指定。

支持多个 config网址 是可选的,现有的 FedCM 实现可以保持不变。

自定义账号标签

借助自定义账号标签,FedCM IDP 可以为账号添加注释,以便 RP 通过在配置文件中指定标签来过滤账号。您可以使用 Domain Hint APILogin Hint API 通过在 navigator.credentials.get() 调用中指定它们来实现类似的过滤,但自定义账号标签可以通过指定配置文件来过滤用户,这在使用多个 config网址 时特别有用。自定义账号标签的另一个不同之处在于,它们是由 IdP 服务器提供的,而不是由 RP(例如登录或网域提示)提供的。

示例

IdP 分别支持面向个人和企业的两个 config网址。消费者配置文件带有 'consumer' 标签,企业配置文件带有 'enterprise' 标签。

采用这种设置时,well-known 文件包含 accounts_endpointlogin_url,以允许多个 config网址。

{
  "provider_urls": [ "https://idp.example/fedcm.json" ],
  "accounts_endpoint": "https://idp.example/accounts",
  "login_url": "https://idp.example/login"
}

在 well-known 文件中提供 accounts_endpoint 时,系统会忽略 provider_urls。RP 可以直接指向 navigator.credentials.get() 调用中的相应配置文件。

使用方配置文件位于 https://idp.example/fedcm.json,其中包含使用 include 指定 'consumer'accounts 属性。

{
  "accounts_endpoint": "https://idp.example/accounts",
  "client_metadata_endpoint": "/client_metadata",
  "login_url": "https://idp.example/login",
  "id_assertion_endpoint": "/assertion",
  "accounts": {
    "include": "consumer"
  }
}

企业配置文件位于 https://idp.example/enterprise/fedcm.json,其中包含使用 include 指定 'enterprise'accounts 属性。

{
  "accounts_endpoint": "https://idp.example/accounts",
  "client_metadata_endpoint": "/enterprise/client_metadata",
  "login_url": "https://idp.example/login",
  "id_assertion_endpoint": "/assertion",
  "accounts": {
    "include": "enterprise"
  }
}

常见 IdP accounts 端点(在此示例中为 https://idp.example/accounts)会返回一个账号列表,其中包含每个账号对应的数组中分配了 labels 的 labels 属性。以下是拥有两个账号的用户的示例响应。一个适用于个人用户,另一个适用于企业用户:

{
 "accounts": [{
   "id": "123",
   "given_name": "John",
   "name": "John Doe",
   "email": "john_doe@idp.example",
   "picture": "https://idp.example/profile/123",
   "labels": ["consumer"]
  }], [{
   "id": "4567",
   "given_name": "Jane",
   "name": "Jane Doe",
   "email": "jane_doe@idp.example",
   "picture": "https://idp.example/profile/4567",
   "labels": ["enterprise"]
  }]
}

当 RP 想要允许 'enterprise' 用户登录时,可以在 navigator.credentials.get() 调用中指定 'enterprise' config网址 'https://idp.example/enterprise/fedcm.json'

let { token } = await navigator.credentials.get({
  identity: {
    providers: [{
      clientId: '1234',
      nonce: '234234',
      configURL: 'https://idp.example/enterprise/fedcm.json',
    },
  }
});

因此,用户只能使用账号 ID '4567' 进行登录。浏览器会静默隐藏 '123' 的账号 ID,以免向用户提供此网站上的身份提供方不支持的账号。

源试用:将 FedCM 用作 Storage Access API 的信任信号

Chrome 126 将开始试行将 FedCM 用作 Storage Access API 的信任信号。经过此次更改,通过 FedCM 之前授予的权限成为通过 Storage Access API 自动批准存储空间访问权限请求的有效理由。

当嵌入的 iframe 想要访问个性化资源时,此属性非常有用:例如,如果 idp.example 嵌入在 rp.example 中,并且需要显示个性化资源。如果浏览器限制对第三方 Cookie 的访问,即使用户使用 idp.example 通过 FedCM 登录了 rp.example,嵌入的 idp.example iframe 也无法请求个性化资源,因为请求不会包含第三方 Cookie。

为此,idp.example 需要通过嵌入在网站上的 iframe 获取存储空间访问权限,而这只能通过权限提示获得。

将 FedCM 用作 Storage Access API 的信任信号后,Storage Access API 权限检查不仅接受存储空间访问提示授予的权限,还接受 FedCM 提示授予的权限。

// In top-level rp.example:

// Ensure FedCM permission has been granted.
const cred = await navigator.credentials.get({
  identity: {
    providers: [{
      configURL: 'https://idp.example/fedcm.json',
      clientId: '123',
    }],
  },
  mediation: 'optional',
});

// In an embedded IdP iframe:

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

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

用户使用 FedCM 登录后,只要 FedCM 身份验证有效,系统就会自动授予相应权限。这意味着,当用户断开连接后,系统会在请求权限时显示提示。

参与源试用

您可以在 Chrome 126 或更高版本中开启 Chrome 标志 chrome://flags#fedcm-authz,在本地试用 FedCM 接续 API 软件包。您还可以尝试在 Chrome 126 或更高版本中启用 #fedcm-with-storage-access-api,以便在本地将 FedCM 用作 Storage Access API 的信任信号。

这些功能也可作为源代码试用版提供。通过源试用,您可以试用新功能,并就其易用性、实用性和效果提供反馈。如需了解详情,请参阅开始使用源代码试用版

如需试用 FedCM Continuation API 软件包源试用,请创建两个源试用令牌:

如果您有兴趣同时启用 Continuation API 和按钮流程,请同时启用 Button Mode API 源代码试用版

如需尝试将 FedCM 用作 Storage Access API 源试用版的信任信号,请执行以下操作:

Continuation API 软件包源试用以及将 FedCM 用作 Storage Access API 源试用的信任信号从 Chrome 126 开始提供。

为 RP 注册第三方来源试用

  1. 前往原始版本试用版注册页面。
  2. 点击注册按钮,然后填写表单以请求令牌。
  3. 将 IdP 的来源输入为网站来源
  4. 选中“第三方匹配”,以便使用 JavaScript 在其他来源上注入令牌。
  5. 点击提交
  6. 将所发放的令牌嵌入到第三方网站中。

如需在第三方网站上嵌入令牌,请将以下代码添加到 IdP 的 JavaScript 库或从 IdP 源提供的 SDK 中。

const tokenElement = document.createElement('meta');
tokenElement.httpEquiv = 'origin-trial';
tokenElement.content = 'TOKEN_GOES_HERE';
document.head.appendChild(tokenElement);

TOKEN_GOES_HERE 替换为您自己的令牌。