使用 FedCM 实现身份解决方案

FedCM(联合凭据管理)是一种可保护隐私的联合身份服务(例如“使用...登录”)方法,可让用户在不与身份服务或网站分享个人信息的情况下登录网站。

FedCM 实现包括针对 IdP(身份提供方)和 RP(依赖方)的几个核心步骤。

IdPs 必须完成以下步骤才能实现 FedCM:

发布商需要完成以下步骤才能在其网站上启用 FedCM:

以 IdP 的形式实现 FedCM

详细了解在 IdP 端实现 FedCM 的步骤。

创建 well-known 文件

为防止跟踪器滥用 API,必须从 IdP 的 eTLD+1/.well-known/web-identity 中提供 well-known 文件。

知名文件可以包含以下属性:

属性 必需 说明
provider_urls 必需 IdP 配置文件路径的数组。如果指定了 accounts_endpointlogin_url,则会被忽略(但仍是必需的)。
accounts_endpoint 推荐,需要 login_url
账号端点的网址。这样,您就可以支持多个配置,前提是每个配置文件都使用相同的 login_urlaccounts_endpoint 网址。

注意:Chrome 132 开始支持此参数。
login_url 推荐,需要 accounts_endpoint 用户登录 IdP 的登录页网址。这样,系统便支持多个配置,前提是每个配置文件都使用相同的 login_urlaccounts_endpoint

注意:Chrome 132 及更高版本支持此参数。

例如,如果 IdP 端点在 https://accounts.idp.example/ 下提供,则必须在 https://idp.example/.well-known/web-identity 下提供 well-known 文件以及 IdP 配置文件。以下是众所周知的文件内容示例:

  {
    "provider_urls": ["https://accounts.idp.example/config.json"]
  }

IdP 可以通过在 well-known 文件中指定 accounts_endpointlogin_url 来容纳多个 IdP 配置文件。
在以下情况下,此功能非常有用:

  • IdP 需要支持多个不同的测试和生产配置。
  • IdP 需要支持每个区域的不同配置(例如,eu-idp.exampleus-idp.example)。

如需支持多种配置(例如,区分测试环境和生产环境),IdP 必须指定 accounts_endpointlogin_url

  {
    // This property is required, but will be ignored when IdP supports
    // multiple configs (when `accounts_endpoint` and `login_url` are
    // specified), as long as `accounts_endpoint` and `login_url` in
    // that config file match those in the well-known file.
    "provider_urls": [ "https://idp.example/fedcm.json" ],

    // Specify accounts_endpoint and login_url properties to support
    // multiple config files.
    // Note: The accounts_endpoint and login_url must be identical
    // across all config files. Otherwise,
    // the configurations won't be supported.
    "accounts_endpoint": "https://idp.example/accounts",
    "login_url": "https://idp.example/login"
  }

创建 IdP 配置文件和端点

IdP 配置文件为浏览器提供了必需端点的列表。IdP 必须托管一个或多个配置文件以及所需的端点和网址。所有 JSON 响应都必须使用 application/json content-type 提供。

配置文件的网址由向 RP 上执行的 navigator.credentials.get() 调用提供的值决定

  const credential = await navigator.credentials.get({
    identity: {
      context: 'signup',
      providers: [{
        configURL: 'https://accounts.idp.example/config.json',
        clientId: '********',
        nonce: '******'
      }]
    }
  });
  const { token } = credential;

RP 会将配置文件的网址传递给 FedCM API 调用,以便用户登录:

  // Executed on RP's side:
  const credential = await navigator.credentials.get({
    identity: {
      context: 'signup',
      providers: [{
        // To allow users to sign in with an IdP using FedCM, RP specifies the IdP's config file URL:
        configURL: 'https://accounts.idp.example/fedcm.json',
        clientId: '********',
  });
  const { token } = credential;

浏览器将使用不含 Origin 标头或 Referer 标头的 GET 请求提取配置文件。请求不含 Cookie,也不遵循重定向。 这样做可有效防止 IdP 了解是谁发出了请求以及哪个 RP 正在尝试建立连接。例如:

  GET /config.json HTTP/1.1
  Host: accounts.idp.example
  Accept: application/json
  Sec-Fetch-Dest: webidentity

IdP 必须实现一个会以 JSON 响应的配置端点。JSON 包含以下属性:

属性 说明
accounts_endpoint(必需) 账号端点的网址。
accounts.include (可选) 自定义账号标签字符串,用于确定使用此配置文件时应返回哪些账号,例如: "accounts": {"include": "developer"}
IdP 可以按如下方式实现自定义账号标签:

例如,IdP 会实现指定了 "accounts": {"include": "developer"}"https://idp.example/developer-config.json" 配置文件。IdP 还会使用账号端点中的 labels 参数将部分账号标记为 "developer" 标签。当 RP 调用 navigator.credentials.get() 并指定 "https://idp.example/developer-config.json" 配置文件时,系统只会返回具有 "developer" 标签的账号。
client_metadata_endpoint(可选) 客户端元数据端点的网址。
id_assertion_endpoint(必需) ID 断言端点的网址。
disconnect(可选) 断开连接端点的网址。
login_url(必需) 用户登录 IdP 的登录页网址
branding(可选) 包含各种品牌选项的对象。
branding.background_color(可选) 用于设置“继续以...身份操作”按钮背景颜色的品牌化选项。使用相关的 CSS 语法,即 hex-colorhsl()rgb()named-color
branding.color(可选) 用于设置“继续以...身份操作”按钮文本颜色的品牌选项。使用相关的 CSS 语法,即 hex-colorhsl()rgb()named-color
branding.icons(可选) 图标对象的数组。这些图标会显示在登录对话框中。图标对象有两个参数:
  • url(必填):图标图片的网址。此功能不支持 SVG 图片。
  • size(可选):应用假定为方形且分辨率为单一的图标尺寸。在被动模式下,此数值必须大于或等于 25px;在主动模式下,此数值必须大于或等于 40px。
modes 包含有关 FedCM 界面在不同模式下显示方式的规范的对象:
  • active
  • passive
modes.active 包含允许在特定模式下自定义 FedCM 行为的属性的对象。modes.activemodes.passive 都可以包含以下参数:
  • supports_use_other_account:布尔值,用于指定用户能否使用与当前登录账号不同的账号登录(如果 IdP 支持多个账号)。

注意:Chrome 132 及更高版本支持“使用其他账号”功能和活跃模式。
modes.passive

以下是身份提供程序的响应正文示例:

  {
    "accounts_endpoint": "/accounts.example",
    "client_metadata_endpoint": "/client_metadata.example",
    "id_assertion_endpoint": "/assertion.example",
    "disconnect_endpoint": "/disconnect.example",
    "login_url": "/login",
    // When RPs use this config file, only those accounts will be
    //returned that include `developer` label in the accounts endpoint.
    "accounts": {"include": "developer"},
    "modes": {
        "active": {
          "supports_use_other_account": true,
        }
    },
    "branding": {
      "background_color": "green",
      "color": "#FFEEAA",
      "icons": [{
        "url": "https://idp.example/icon.ico",
        "size": 25
      }]
    }
  }

浏览器提取配置文件后,会向 IdP 端点发送后续请求:

IdP 端点
IdP 端点

使用其他账号

如果 IdP 支持多个账号或替换现有账号,用户可以切换到与其当前登录的账号不同的账号。

如需允许用户选择其他账号,IdP 必须在配置文件中指定此功能:

  {
    "accounts_endpoint" : "/accounts.example",
    "modes": {
      "active": {
        // Allow the user to choose other account (false by default)
        "supports_use_other_account": true
      }
      // "passive" mode can be configured separately
    }
  }

账号端点

IdP 的账号端点会返回用户在 IdP 上登录的账号列表。如果 IdP 支持多个账号,此端点将返回所有已登录的账号。

浏览器发送包含 SameSite=None 的 Cookie 的 GET 请求,但不包含 client_id 参数、Origin 标头或 Referer 标头。这可有效防止 IdP 了解用户尝试登录哪个 RP。例如:

  GET /accounts.example HTTP/1.1
  Host: accounts.idp.example
  Accept: application/json
  Cookie: 0x23223
  Sec-Fetch-Dest: webidentity

收到请求后,服务器应:

  1. 验证请求是否包含 Sec-Fetch-Dest: webidentity HTTP 标头。
  2. 将会话 Cookie 与已登录账号的 ID 进行匹配。
  3. 响应并提供账号列表。

浏览器预期 JSON 响应包含一个 accounts 属性,其中包含具有以下属性的账号信息数组:

属性 说明
id(必需) 用户的唯一 ID。
name(必需) 用户的名字和姓氏。
email(必需) 用户的电子邮件地址。
given_name(可选) 用户的名字。
picture(可选) 用户头像图片的网址。
approved_clients(可选) 用户已注册的 RP 客户端 ID 的数组。
login_hints(可选) 一个数组,其中包含 IdP 支持的用于指定账号的所有可能的过滤条件类型。RP 可以使用 loginHint 属性调用 navigator.credentials.get(),以有选择地显示指定的账号。
domain_hints(可选) 账号关联的所有网域的数组。RP 可以使用 domainHint 属性调用 navigator.credentials.get() 来过滤账号。
labels(可选) 与账号关联的自定义账号标签的字符串数组。
IdP 可以按如下方式实现自定义账号标签:
  • 在 accounts 端点中指定账号标签(使用此 labels 参数)。
  • 为每个特定标签创建一个配置文件

例如,IdP 会实现指定了 "accounts": {"include": "developer"}https://idp.example/developer-config.json 配置文件。IdP 还会使用 账号端点中的 labels 参数将部分账号标记为 "developer" 标签。当 RP 调用 navigator.credentials.get() 并指定 https://idp.example/developer-config.json 配置文件时,系统只会返回具有 "developer" 标签的账号。

自定义账号标签与登录提示和域名提示不同,因为它们由 IdP 服务器完全维护,RP 仅指定要使用的配置文件。

响应正文示例:

  {
    "accounts": [{
      "id": "1234",
      "given_name": "John",
      "name": "John Doe",
      "email": "john_doe@idp.example",
      "picture": "https://idp.example/profile/123",
      // Ids of those RPs where this account can be used
      "approved_clients": ["123", "456", "789"],
      // This account has 'login_hints`. When an RP calls `navigator.credentials.get()`
      // with a `loginHint` value specified, for example, `exampleHint`, only those
      // accounts will be shown to the user whose 'login_hints' array contains the `exampleHint`.
      "login_hints": ["demo1", "exampleHint"],
      // This account is labelled. IdP can implement a specific config file for a
      // label, for example, `https://idp.example/developer-config.json`. Like that
      // RPs can filter out accounts by calling `navigator.credentials.get()` with
      // `https://idp.example/developer-config.json` config file.
      "labels": ["hr", "developer"]
    }, {
      "id": "5678",
      "given_name": "Johnny",
      "name": "Johnny",
      "email": "johnny@idp.example",
      "picture": "https://idp.example/profile/456",
      "approved_clients": ["abc", "def", "ghi"],
      "login_hints": ["demo2"],
      "domain_hints": ["@domain.example"]
    }]
  }

如果用户未登录,请响应 HTTP 401(未经授权)。

返回的账号列表将由浏览器使用,RP 无法使用。

ID 断言端点

IdP 的 ID 断言端点会为其已登录的用户返回断言。当用户使用 navigator.credentials.get() 调用登录 RP 网站时,浏览器会向此端点发送包含 SameSite=None 的 Cookie 和内容类型为 application/x-www-form-urlencodedPOST 请求,其中包含以下信息:

属性 说明
client_id(必需) RP 的客户端标识符。
account_id(必需) 登录用户的唯一 ID。
disclosure_text_shown 结果为 "true""false" 字符串(而非布尔值)。在以下情况下,结果为 "false"
  • 如果披露文本未显示,是因为 RP 的客户 ID 包含在 accounts 端点响应的 approved_clients 属性列表中。
  • 如果披露文本未显示,是因为浏览器在过去观察到在没有 approved_clients 的情况下发生了注册时刻。
  • 如果 fields 参数不包含三个字段(“name”“email”和“picture”)之一或全部,例如 fields=[ ]fields=['name', 'picture']。这是为了与旧版 IdP 实现向后兼容,因为这些实现会预期披露字符串始终包含所有三个字段。
is_auto_selected 如果对 RP 执行了自动重新身份验证is_auto_selected 表示 "true"。否则为 "false"。这有助于支持更多与安全相关的功能。例如,有些用户可能更倾向于更高级别的安全性,这需要在身份验证过程中明确由用户参与。如果 IdP 收到没有此类中介的令牌请求,则可以以其他方式处理该请求。例如,返回一个错误代码,以便 RP 可以使用 mediation: required 再次调用 FedCM API。
fields(可选) 字符串数组,用于指定 RP 需要 IdP 与其分享的用户信息(“name”“email”“picture”)。
浏览器将发送 fieldsdisclosure_text_showndisclosure_shown_for,在 POST 请求中列出指定字段,如以下示例所示。

注意:Chrome 132 开始支持“Fields”参数。
params(可选) 允许指定其他自定义键值对参数的任何有效 JSON 对象,例如:
  • scope:一个字符串值,包含 RP 需要请求的其他权限,例如 "drive.readonly calendar.readonly"
  • nonce:RP 提供的随机字符串,用于确保针对此特定请求发出响应。防范重放攻击。
  • 其他自定义键值对参数。
当浏览器发送 POST 请求时,params 值将序列化为 JSON,然后进行百分比编码

注意:Chrome 132 及更高版本支持 Parameters API。

HTTP 标头示例:

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

  // disclosure_text_shown is set to 'false', as the 'name' field value is missing in 'fields' array
  // params value is serialized to JSON and then percent-encoded.
  account_id=123&client_id=client1234&disclosure_text_shown=false&is_auto_selected=true&params=%22%7B%5C%22nonce%5C%22%3A%5C%22nonce-value%5C%22%7D%22.%0D%0A4&disclosure_text_shown=true&fields=email,picture&disclosure_shown_for=email,picture

收到请求后,服务器应:

  1. 使用 CORS(跨源资源共享)响应请求。
  2. 验证请求是否包含 Sec-Fetch-Dest: webidentity HTTP 标头。
  3. Origin 标头与由 client_id 确定的 RP 源进行匹配。 如果不匹配,请拒绝。
  4. account_id 与已登录账号的 ID 进行匹配。如果不一致,请拒绝。
  5. 使用令牌进行响应。如果请求被拒绝,请使用错误响应进行响应。

IdP 可以决定如何签发令牌。通常,它使用账号 ID、客户端 ID、签发者来源和 Nonce 等信息进行签名,以便 RP 可以验证令牌是否真实。

浏览器希望 JSON 响应包含以下属性:

属性 说明
token 令牌是包含与身份验证相关的声明的字符串。
continue_on 用于启用多步登录流程的重定向网址。

浏览器会将返回的令牌传递给 RP,以便 RP 验证身份验证。

  {
    // IdP can respond with a token to authenticate the user
    "token": "***********"
  }
“继续操作”功能

IdP 可以在 ID 断言端点响应中提供重定向网址,以启用多步登录流程。当 IdP 需要请求更多信息或权限时,此功能非常有用,例如:

  • 访问用户服务器端资源的权限。
  • 验证联系信息是否是最新的。
  • 家长控制。

身份断言端点可以返回一个 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 popup window:
    "continue_on": "https://idp.example/continue_on_url"
  }

如果响应包含 continue_on 参数,系统会打开一个新的弹出式窗口,并将用户定向到指定的路径。用户与 continue_on 页面互动后,IdP 应调用 IdentityProvider.resolve(),并将令牌作为参数传递,以便解析原始 navigator.credentials.get() 调用的 promise:

  document.getElementById('example-button').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.resolve() 调用是父窗口 (RP) 和弹出式窗口 (IdP) 进行通信的唯一方式。
如果用户拒绝请求,IdP 可以通过调用 IdentityProvider.close() 来关闭窗口。

  IdentityProvider.close();

Continuation API 需要用户明确互动(点击)才能正常运行。Continuation API 与不同中介模式的运作方式如下:

  • 被动模式下:
    • mediation: 'optional'(默认):Continuation API 仅适用于用户手势,例如点击页面或 FedCM 界面上的按钮。如果在没有用户手势的情况下触发自动重新身份验证,系统不会打开任何弹出式窗口,并且会拒绝该 Promise。
    • mediation: 'required':始终要求用户互动,因此 Continuation API 始终有效。
  • 活动模式下:
    • 始终需要用户激活。Continuation API 是兼容的。

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

  IdentityProvider.resolve(token, {accountId: '1234');
返回错误响应

id_assertion_endpoint 还可以返回“错误”响应,其中包含两个可选字段:

  • code:IdP 可以从 OAuth 2.0 指定的错误列表中选择一个已知错误(invalid_requestunauthorized_clientaccess_deniedserver_errortemporarily_unavailable),也可以使用任何任意字符串。如果是后者,Chrome 会使用通用错误消息呈现错误界面,并将代码传递给 RP。
  • url:用于标识包含错误相关信息的直观易懂的网页,以向用户提供有关错误的更多信息。此字段对用户很有用,因为浏览器无法在内置界面中提供丰富的错误消息。例如:后续步骤的链接或客户服务联系信息。如果用户想要详细了解错误以及如何解决错误,可以通过浏览器界面访问提供的页面,了解更多详情。网址必须与 IdP configURL 位于同一网站上。
  // id_assertion_endpoint response
  {
    "error" : {
      "code": "access_denied",
      "url" : "https://idp.example/error?type=access_denied"
    }
  }

自定义账号标签

借助自定义账号标签,IdP 可以为用户账号添加标签注释,RP 可以选择仅提取具有特定标签的账号,方法是为该特定标签指定 configURL。当 RP 需要按特定条件滤除账号时,这会非常有用,例如,仅显示角色专用账号(例如 "developer""hr")。

您也可以使用网域提示登录提示功能进行类似的过滤,只需在 navigator.credentials.get() 调用中指定这些功能即可。不过,自定义账号标签可以通过指定配置文件来过滤用户,这在使用多个 config网址 时特别有用。自定义账号标签也不同,因为它们是从 IdP 服务器提供的,而不是从 RP 提供的,例如登录提示或网域提示。

假设某个 IdP 想要区分 "developer" 账号和 "hr" 账号。为此,IdP 需要分别为 "developer""hr" 支持两个 config网址:

  • 开发者配置文件 https://idp.example/developer/fedcm.json 具有 "developer" 标签,企业配置文件 https://idp.example/hr/fedcm.json 具有 "hr" 标签,如下所示:
  // The developer config file at `https://idp.example/developer/fedcm.json`
  {
    "accounts_endpoint": "https://idp.example/accounts",
    "client_metadata_endpoint": "/client_metadata",
    "login_url": "https://idp.example/login",
    "id_assertion_endpoint": "/assertion",
    "accounts": {
      // Account label
      "include": "developer"
    }
  }
  // The hr config file at `https://idp.example/hr/fedcm.json`
  {
    "accounts_endpoint": "https://idp.example/accounts",
    "client_metadata_endpoint": "/client_metadata",
    "login_url": "https://idp.example/login",
    "id_assertion_endpoint": "/assertion",
    "accounts": {
      // Account label
      "include": "hr"
    }
  }
  • 采用这种设置时,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"
  }
  • 常见 IdP accounts 端点(在此示例中为 https://idp.example/accounts)会返回一个账号列表,其中包含每个账号对应的 labels 属性,该属性在数组中包含分配的标签:
  {
  "accounts": [{
    "id": "123",
    "given_name": "John",
    "name": "John Doe",
    "email": "john_doe@idp.example",
    "picture": "https://idp.example/profile/123",
    "labels": ["developer"]
    }], [{
    "id": "4567",
    "given_name": "Jane",
    "name": "Jane Doe",
    "email": "jane_doe@idp.example",
    "picture": "https://idp.example/profile/4567",
    "labels": ["hr"]
    }]
  }

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

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

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

  • 标签是字符串。如果 labels 数组或 include 字段包含非字符串内容,则系统会忽略该内容。
  • 如果未在 configURL 中指定任何标签,则 FedCM 账号选择器中将显示所有账号。
  • 如果未为账号指定任何标签,则只有在 configURL 也未指定标签时,该账号才会显示在账号选择器中。
  • 如果在被动模式下没有任何账号与请求的标签匹配(类似于“域名提示”功能),FedCM 对话框会显示登录提示,以便用户登录 IdP 账号。对于活动模式,系统会直接打开登录弹出式窗口。

断开端点

通过调用 IdentityCredential.disconnect(),浏览器会向此断开连接端点发送包含 SameSite=None 和内容类型为 application/x-www-form-urlencoded 的跨源 POST 请求,其中包含以下信息:

属性 说明
account_hint IdP 账号的提示。
client_id RP 的客户端标识符。
  POST /disconnect.example HTTP/1.1
  Host: idp.example
  Origin: rp.example
  Content-Type: application/x-www-form-urlencoded
  Cookie: 0x123
  Sec-Fetch-Dest: webidentity

  account_hint=account456&client_id=rp123

收到请求后,服务器应:

  1. 使用 CORS(跨源资源共享)响应请求。
  2. 验证请求是否包含 Sec-Fetch-Dest: webidentity HTTP 标头。
  3. Origin 标头与由 client_id 确定的 RP 源进行匹配。 如果不匹配,请拒绝。
  4. account_hint 与已登录账号的 ID 进行匹配。
  5. 解除用户账号与 RP 的关联。
  6. 以 JSON 格式向浏览器响应已识别的用户账号信息。

响应 JSON 载荷示例如下:

  {
    "account_id": "account456"
  }

相反,如果 IdP 希望浏览器解除与 RP 关联的所有账号,请传递与任何账号 ID 都不匹配的字符串,例如 "*"

客户端元数据端点

IdP 的客户端元数据端点会返回依赖方的元数据,例如 RP 的隐私权政策、服务条款和徽标图标。RP 应提前向 IdP 提供指向其隐私权政策和服务条款的链接。如果用户尚未在 RP 上使用 IdP 注册,系统会在登录对话框中显示这些链接。

浏览器使用不含 Cookie 的 client_id navigator.credentials.get 发送 GET 请求。例如:

  GET /client_metadata.example?client_id=1234 HTTP/1.1
  Host: accounts.idp.example
  Origin: https://rp.example/
  Accept: application/json
  Sec-Fetch-Dest: webidentity

收到请求后,服务器应:

  1. 确定 client_id 的 RP。
  2. 使用客户端元数据进行响应。

客户端元数据端点的属性包括:

属性 说明
privacy_policy_url(可选) RP 隐私权政策网址。
terms_of_service_url(可选) RP 服务条款网址。
icons(可选) 对象数组,例如 [{ "url": "https://rp.example/rp-icon.ico", "size": 40}]

浏览器会要求从该端点返回 JSON 响应:

  {
    "privacy_policy_url": "https://rp.example/privacy_policy.html",
    "terms_of_service_url": "https://rp.example/terms_of_service.html",
    "icons": [{
          "url": "https://rp.example/rp-icon.ico",
          "size": 40
      }]
  }

返回的客户端元数据将由浏览器使用,RP 无法使用。

登录网址

此端点用于让用户登录 IdP。

使用 Login Status API 时,IdP 必须向浏览器告知用户的登录状态。不过,状态可能会不同步,例如在会话过期时。在这种情况下,浏览器可以动态允许用户通过使用 idp 配置文件login_url 指定的登录页面网址登录 IdP。

FedCM 对话框会显示一条提示登录的消息,如下图所示。

A
一个 FedCM 对话框,提示用户登录 IdP。

当用户点击 Continue 按钮时,浏览器会打开一个弹出式窗口,显示 IdP 的登录页面。

FedCM 对话框示例。
点击“登录 IdP”按钮后显示的示例对话框。

该对话框是一个包含第一方 Cookie 的常规浏览器窗口。对话框中发生的任何情况都取决于 IdP,并且没有窗口句柄可用于向 RP 页面发出跨源通信请求。用户登录后,IdP 应:

  • 发送 Set-Login: logged-in 标头或调用 navigator.login.setStatus("logged-in") API 以告知浏览器用户已登录。
  • 调用 IdentityProvider.close() 以关闭对话框。
用户使用 FedCM 登录 IdP 后,再登录 RP。

告知浏览器用户的登录状态

Login Status API 是一种机制,网站(尤其是 IdP)可以通过该机制告知浏览器用户在 IdP 上的登录状态。借助此 API,浏览器可以减少对 IdP 的不必要请求,并缓解潜在的时间攻击

当用户在 IdP 上登录或退出所有 IdP 账号时,IdP 可以通过发送 HTTP 标头或调用 JavaScript API 向浏览器发送用户的登录状态信号。对于每个 IdP(通过其配置网址进行标识),浏览器都会保留一个三态变量来表示登录状态,可能的值如下:

  • logged-in
  • logged-out
  • unknown(默认)
登录状态 说明
logged-in 当用户的登录状态设置为 logged-in 时,调用 FedCM 的 RP 会向 IdP 的账号端点发出请求,并在 FedCM 对话框中向用户显示可用账号。
logged-out 当用户的登录状态为 logged-out 时,调用 FedCM 会静默失败,而不会向 IdP 的账号端点发出请求。
unknown(默认) 在 IdP 使用 Login Status API 发送信号之前,系统会设置 unknown 状态。当状态为 unknown 时,浏览器会向 IdP 的账号端点发出请求,并根据账号端点的响应更新状态。

如需指示用户已登录,请在顶级导航中发送 Set-Login: logged-in HTTP 标头,或在 IdP 源中发送同一网站子资源请求:

  Set-Login: logged-in

或者,您也可以在顶级导航栏中从 IdP 来源调用 JavaScript 方法 navigator.login.setStatus('logged-in')

  navigator.login.setStatus('logged-in')

用户的登录状态将设为 logged-in

如需指示用户已从其所有账号退出,请在顶级导航中发送 Set-Login: logged-out HTTP 标头,或在 IdP 源发送同一网站子资源请求:

  Set-Login: logged-out

或者,您也可以在顶级导航栏中的 IdP 来源中调用 JavaScript API navigator.login.setStatus('logged-out')

  navigator.login.setStatus('logged-out')

用户的登录状态将设为 logged-out

在 IdP 使用 Login Status API 发送信号之前,系统会设置 unknown 状态。浏览器向 IdP 的账号端点发出请求,并根据账号端点的响应更新状态:

  • 如果端点返回有效账号列表,请将状态更新为 logged-in,然后打开 FedCM 对话框以显示这些账号。
  • 如果端点未返回任何账号,请将状态更新为 logged-out 并使 FedCM 调用失败。

允许用户通过动态登录流程登录

即使 IdP 会持续向浏览器告知用户的登录状态,但状态也可能会不同步,例如在会话过期时。当登录状态为 logged-in 时,浏览器会尝试向账号端点发送包含凭据的请求,但由于会话不再可用,服务器不会返回任何账号。在这种情况下,浏览器可以动态让用户通过弹出式窗口登录 IdP

将 FedCM 实现为 RP

IdP 的配置和端点可用后,RP 可以调用 navigator.credentials.get() 来请求允许用户使用 IdP 登录 RP。

在调用该 API 之前,您需要确认 FedCM 是否可在用户的浏览器中使用。如需检查 FedCM 是否可用,请在 FedCM 实现中封装以下代码:

  if ('IdentityCredential' in window) {
    // If the feature is available, take action
  } else {
    // FedCM is not supported, use a different identity solution
  }

如需允许用户使用 FedCM 在 RP 上登录 IdP,RP 可以调用 navigator.credentials.get(),例如:

  const credential = await navigator.credentials.get({
    identity: {
      context: 'signin',
      providers: [{
        configURL: 'https://accounts.idp.example/config.json',
        clientId: '********',
        mode: 'active',
        params: {
          nonce: '******'
        }
      }]
    }
  });
  const { token } = credential;

上下文属性

借助可选的 context 属性,RP 可以修改 FedCM 对话框界面中的字符串(例如“登录 rp.example…”、“使用 idp.example…”),以适应预定义的身份验证上下文。context 属性可以具有以下值:

  • signin(默认)
  • signup
  • use
一张图表,说明了 FedCM 对话框的界面组件:左上角显示了一个图标。该图标右侧是显示“使用 IdP 登录 RP”消息的上下文组件。底部是带有自定义文本和背景颜色的“Continue”(继续)按钮。
如何将品牌信息应用于 FedCM 对话框

例如,将 context 设置为 use 会导致以下消息:

显示自定义上下文消息的 FedCM 对话框:上下文消息显示“使用”FedCM,而不是“登录”FedCM。
显示自定义上下文消息的 FedCM 对话框。

浏览器会根据 accounts list 端点响应中是否存在 approved_clients 来不同地处理注册和登录用例。如果用户已注册 RP,浏览器将不会显示披露文本“To continue with ....”(如要继续使用...)。
providers 属性接受一个 IdentityProvider 对象数组,这些对象具有以下属性:

Providers 属性

providers 属性接受一个 IdentityProvider 对象数组,该数组具有以下属性:

属性 说明
configURL(必需) IdP 配置文件的完整路径。
clientId(必需) IdP 签发的 RP 客户端标识符。
nonce(可选) 一个随机字符串,用于确保系统针对此特定请求发出响应。防范重放攻击。
loginHint(可选) 通过指定 accounts 端点提供的 login_hints 值之一,FedCM 对话框会选择性地显示指定的账号。
domainHint(可选) 通过指定 accounts 端点提供的 domain_hints 值之一,FedCM 对话框会选择性地显示指定的账号。
mode(可选) 用于指定 FedCM 的界面模式的字符串。可以是以下值之一:
  • "active":FedCM 提示必须由用户互动(例如点击按钮)触发。
  • "passive":FedCM 提示将在不直接与用户互动的情况下发起。
如需详细了解主动模式和被动模式之间的区别,请参阅概览页面

注意:Chrome 132 开始支持 mode 参数。
fields(可选) 字符串数组,用于指定 RP 需要 IdP 与其分享的用户信息(“name”“email”“picture”)。
注意:Chrome 132 及更高版本支持 Field API。
parameters(可选) 允许指定其他键值对参数的自定义对象:
  • scope:一个字符串值,包含 RP 需要请求的其他权限,例如 "drive.readonly calendar.readonly"
  • nonce:一个随机字符串,用于确保系统针对此特定请求发出响应。防范重放攻击。
  • 其他自定义键值对参数。

注意:Chrome 132 开始支持 parameters

活动模式

FedCM 支持不同的用户体验模式配置。被动模式是默认模式,开发者无需对其进行配置。

如需在活动模式下使用 FedCM,请执行以下操作:

  1. 检查用户浏览器中功能的适用范围。
  2. 使用瞬时用户手势(例如按钮点击)调用 API。
  3. mode 参数传递给 API 调用:
  let supportsFedCmMode = false;
  try {
    navigator.credentials.get({
      identity: Object.defineProperty(
        // Check if this Chrome version supports the Mode API.
        {}, 'mode', {
          get: function () { supportsFedCmMode = true; }
        }
      )
    });
  } catch(e) {}

  if (supportsFedCmMode) {
    // The button mode is supported. Call the API with mode property:
    return await navigator.credentials.get({
      identity: {
        providers: [{
          configURL: 'https://idp.example/config.json',
          clientId: '123',
        }],
        // The 'mode' value defines the UX mode of FedCM.
        // - 'active': Must be initiated by user interaction (e.g., clicking a button).
        // - 'passive': Can be initiated without direct user interaction.
        mode: 'active'
      }
    });
  }

活动模式下的自定义图标

在活动模式下,IdP 可以直接在客户端元数据端点响应中添加 RP 的官方徽标图标。RP 必须提前提供其品牌数据。

从跨源 iframe 内调用 FedCM

您可以使用 identity-credentials-get 权限政策从跨源 iframe 中调用 FedCM(如果父级框架允许)。为此,请将 allow="identity-credentials-get" 属性附加到 iframe 代码,如下所示:

  <iframe src="https://fedcm-cross-origin-iframe.glitch.me" allow="identity-credentials-get"></iframe>

您可以在示例中查看其实际运作情况。

(可选)如果父级帧想要限制调用 FedCM 的来源,请发送包含允许来源列表的 Permissions-Policy 标头。

  Permissions-Policy: identity-credentials-get=(self "https://fedcm-cross-origin-iframe.glitch.me")

如需详细了解权限政策的运作方式,请参阅使用权限政策控制浏览器功能

Login Hint API

使用登录提示,RP 可以建议用户应使用哪个账号登录。这对于重新验证不确定自己之前使用过哪个账号的用户很有帮助。

RP 可以通过将 loginHint 属性与从账号列表端点提取的 login_hints 值之一一起调用 navigator.credentials.get(),以选择性地显示特定账号,如以下代码示例所示:

  return await navigator.credentials.get({
    identity: {
      providers: [{
        configURL: 'https://idp.example/manifest.json',
        clientId: '123',
        // Accounts endpoint can specify a 'login_hints' array for an account.
        // When RP specifies a 'exampleHint' value, only those accounts will be
        // shown to the user whose 'login_hints' array contains the 'exampleHint'
        // value
        loginHint : 'exampleHint'
      }]
    }
  });

如果没有账号与 loginHint 匹配,FedCM 对话框会显示登录提示,以便用户登录与 RP 请求的提示相符的 IdP 账号。当用户点按提示时,系统会打开一个弹出式窗口,其中包含配置文件中指定的登录网址。然后,在链接后附加登录提示和网域提示查询参数。

Domain Hint API

RP 可以选择仅显示与特定网域关联的账号。对于仅限于企业网域的 RP,这可能很有用。

如需仅显示特定网域账号,RP 应使用从账号列表端点提取的 domain_hints 值之一调用 navigator.credentials.get()domainHint 属性,如以下代码示例所示:

  return await navigator.credentials.get({
    identity: {
      providers: [{
        configURL: 'https://idp.example/manifest.json',
        clientId: 'abc',
        // Accounts endpoint can specify a 'domain_hints' array for an account.
        // When RP specifies a '@domain.example' value, only those accounts will be
        // shown to the user whose 'domain_hints' array contains the
        // '@domain.example' value
        domainHint : '@domain.example'
      }]
    }
  });

如果没有账号与 domainHint 匹配,FedCM 对话框会显示登录提示,以便用户登录与 RP 请求的提示相符的 IdP 账号。当用户点按提示时,系统会打开一个弹出式窗口,其中包含配置文件中指定的登录网址。然后,在链接后附加登录提示和网域提示查询参数。

当没有账号与 domainHint 匹配时显示的登录提示示例。
当没有账号与 domainHint 匹配时显示的登录提示示例。

自定义参数

借助“自定义参数”功能,RP 可以向ID 断言端点提供其他键值对参数。借助 Parameters API,RP 可以向 IdP 传递额外的参数,以请求基本登录以外的资源权限。在以下情况下,传递其他参数会很有用:

  • RP 需要动态请求 IdP 拥有的其他权限,例如结算地址或日历访问权限。用户可以通过使用“继续操作”功能启动的 IdP 控制的用户体验流程授予这些权限,然后 IdP 会分享这些信息。

如需使用该 API,RP 会在 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'
        }
      },
    }
  });

浏览器会自动将其转换为向 IdP 发出的 POST 请求,其中参数为单个经过网址编码的 JSON 序列化对象:

  // The assertion endpoint is drawn from the config file
  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

  // params are translated into urlencoded version of `{"IDP_SPECIFIC_PARAM":"1","foo":"bar"}`
  account_id=123&client_id=client1234&params=%22%7B%5C%22IDP_SPECIFIC_PARAM%5C%22%3A1%2C%5C%22foo%5C%22%3A%5C%22BAR%5C%22%7D%22.

如果 RP 需要任何其他权限,IdP 可以提供重定向链接。例如,在 node.js 中:

  if (rpRequestsPermissions) {
    // Response with a URL if the RP requests additional permissions
    return res.json({
      continue_on: '/example-redirect',
    });
  }

字段

RP 可以指定他们需要 IdP 与其共享的用户信息(姓名、电子邮件地址和个人资料照片的任意组合)。系统会在 FedCM 对话框的披露信息界面中显示所请求的信息。如果用户选择登录,系统会显示一条消息,告知用户 idp.example 会与 rp.example 共享所请求的信息。

显示披露信息消息的 FedCM 活跃模式对话框。如要继续,身份提供程序会与该网站分享用户的电子邮件地址和个人资料照片。
处于活动模式下的披露消息:RP 请求 IdP 仅分享用户电子邮件地址和个人资料照片。

如需使用“字段”功能,RP 应在 navigator.credentials.get() 调用中添加 fields 数组。这些字段可以包含 nameemailpicture 的任何排列组合。未来,此列表可能会扩展为包含更多值。包含 fields 的请求如下所示:

  let { token } = await navigator.credentials.get({
    identity: {
      providers: [{
        // RP requests the IdP to share only user email and profile picture
        fields: [ 'email', 'picture'],
        clientId: '1234',
        configURL: 'https://idp.example/fedcm.json',

      },
    }
  });

浏览器会自动将其转换为向ID 断言端点发出的 HTTP 请求,其中包含 RP 指定的 fields 参数,以及浏览器在 disclosure_shown_for 参数中向用户披露的字段。为了实现向后兼容,如果已显示披露文本,并且请求的字段包括 三个字段'name''email''picture',浏览器还会发送 disclosure_text_shown=true

  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

  // The RP only requested to share email and picture. The browser will send `disclosure_text_shown=false`, as the 'name' field value is missing
  account_id=123&client_id=client1234&disclosure_text_shown=false&fields=email,picture&disclosure_shown_for=email,picture

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

不显示披露信息界面消息的 FedCM 被动模式对话框。
在被动模式下,系统不会显示披露信息消息。在按钮流程中,系统会完全跳过披露声明界面。

即使 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

显示错误消息

有时,身份提供方可能出于合法原因无法签发令牌,例如客户端未经授权或服务器暂时不可用。如果 IdP 返回“错误”响应,RP 可以捕获该响应,Chrome 可以通过显示包含 IdP 提供的错误信息的浏览器界面来通知用户。

A
FedCM 对话框,显示用户登录尝试失败后的错误消息。此字符串与错误类型相关联。
  try {
    const cred = await navigator.credentials.get({
      identity: {
        providers: [
          {
            configURL: 'https://idp.example/manifest.json',
            clientId: '1234',
          },
        ],
      }
    });
  } catch (e) {
    const code = e.code;
    const url = e.url;
  }

在初始身份验证后自动重新验证用户身份

FedCM 自动重新身份验证(简称“auto-reauthn”)可让用户在使用 FedCM 进行初始身份验证后再次访问时自动重新进行身份验证。这里的“初始身份验证”是指用户在同一浏览器实例中首次点按 FedCM 登录对话框中的“Continue as...” 按钮,创建账号或登录 RP 的网站。

虽然在用户创建联合账号以防止跟踪(这是 FedCM 的主要目标之一)之前,明确的用户体验很有意义,但在用户完成一次体验后,再次执行此体验会不必要地繁琐:在用户授予允许 RP 与 IdP 之间通信的权限后,再强制用户针对他们之前已确认的内容再次明确确认,对隐私或安全没有任何好处。

使用自动重新授权时,浏览器会根据您在调用 navigator.credentials.get() 时为 mediation 指定的选项来更改其行为。

  const cred = await navigator.credentials.get({
    identity: {
      providers: [{
        configURL: 'https://idp.example/fedcm.json',
        clientId: '1234',
      }],
    },
    mediation: 'optional', // this is the default
  });

  // `isAutoSelected` is `true` if auto-reauthn was performed.
  const isAutoSelected = cred.isAutoSelected;

mediationCredential Management API 中的属性,其行为与 PasswordCredentialFederatedCredential 相同,并且 PublicKeyCredential 也对其提供部分支持。该属性接受以下四个值:

  • 'optional'(默认):尽可能自动重新授权,否则需要中介。我们建议您在登录页面上选择此选项。
  • 'required':始终需要中介才能继续,例如点击界面上的“继续”按钮。如果您希望用户每次需要进行身份验证时都明确授予权限,请选择此选项。
  • 'silent':如果可能,自动重新授权;如果不可能,则静默失败,无需中介。我们建议您在专用登录页面以外但希望用户保持登录状态的页面(例如配送网站上的商品页面或新闻网站上的文章页面)上选择此选项。
  • 'conditional':用于 WebAuthn,目前不适用于 FedCM。

在进行此调用时,会在以下情况下自动重新授权:

  • FedCM 可供使用。例如,用户尚未在设置中全局停用 FedCM 或停用 RP。
  • 用户仅使用一个具有 FedCM API 的账号在此浏览器上登录了该网站。
  • 用户已使用该账号登录 IdP。
  • 自动重新授权未在过去 10 分钟内发生。
  • RP 在之前的登录之后未调用 navigator.credentials.preventSilentAccess()

满足这些条件后,系统会在调用 FedCM navigator.credentials.get() 后立即尝试自动重新对用户进行身份验证。

mediation: optional 时,由于只有浏览器知道的原因,自动重新授权可能不可用;RP 可以通过检查 isAutoSelected 属性来检查是否执行了自动重新授权。

这有助于评估 API 性能并相应地改进用户体验。此外,当该功能不可用时,系统可能会提示用户通过显式用户中介登录,这是一种包含 mediation: required 的流程。

用户通过 FedCM 自动重新进行身份验证。

使用 preventSilentAccess() 强制执行中介

在用户退出账号后立即自动重新对其进行身份验证,会给用户带来不太好的体验。因此,FedCM 在自动重新授权后会有一个 10 分钟的静默期,以防止出现这种行为。这意味着,除非用户在 10 分钟内重新登录,否则系统每 10 分钟最多只会自动重新授权一次。当用户明确退出 RP(例如,点击“退出”按钮)时,RP 应调用 navigator.credentials.preventSilentAccess() 以明确请求浏览器停用自动重新授权。

  function signout() {
    navigator.credentials.preventSilentAccess();
    location.href = '/signout';
  }

用户可以在设置中选择停用自动重新授权

用户可以在“设置”菜单中选择停用自动重新授权:

  • 在桌面版 Chrome 中,依次选择 chrome://password-manager/settings > 自动登录。
  • 在 Android 版 Chrome 中,依次打开设置 > 密码管理工具 > 点按右上角的齿轮图标 > 自动登录。

通过停用此切换开关,用户可以完全停用自动重新授权行为。如果用户在 Chrome 实例中登录了 Google 账号并启用了同步功能,系统会存储此设置并在设备间同步。

断开 IdP 与 RP 之间的连接

如果用户之前通过 FedCM 使用 IdP 登录了 RP,浏览器会在本地将该关系记忆为已关联账号的列表。RP 可以通过调用 IdentityCredential.disconnect() 函数来发起断开连接。此函数可从顶级 RP 帧调用。RP 需要传递 configURL(它在 IdP 下使用的 clientId)和 accountHint,以便断开 IdP 的连接。账号提示可以是任意字符串,只要断开连接端点可以识别账号即可,例如电子邮件地址或用户 ID,该 ID 不一定与账号列表端点提供的账号 ID 相匹配:

  // Disconnect an IdP account 'account456' from the RP 'https://idp.com/'. This is invoked on the RP domain.
  IdentityCredential.disconnect({
    configURL: 'https://idp.com/config.json',
    clientId: 'rp123',
    accountHint: 'account456'
  });

IdentityCredential.disconnect() 会返回 Promise。此 promise 可能会因以下原因抛出异常:

  • 用户尚未通过 FedCM 使用 IdP 登录 RP。
  • 从不含 FedCM 权限政策的 iframe 中调用 API。
  • config网址 无效或缺少断开连接端点。
  • 内容安全政策 (CSP) 检查失败。
  • 有一个待处理的解除关联请求。
  • 用户已在浏览器设置中停用 FedCM。

身份提供方的断开连接端点返回响应时,浏览器上的 RP 和 IdP 会断开连接,并且 Promise 会解析。已解除关联的账号的 ID 在解除关联端点的响应中指定。