Federated Credential Management API

Draft Community Group Report,

This version:
https://fedidcg.github.io/FedCM/
Test Suite:
https://github.com/web-platform-tests/wpt/blob/master/credential-management/
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google Inc.)

Abstract

A Web Platform API that allows users to login to websites with their federated accounts in a privacy preserving manner.

Status of this document

This specification was published by the Federated Identity Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

This section is non-normative.

As the web has evolved there have been ongoing privacy-oriented changes (e.g Safari, Firefox, Chrome) and changes to the underlying privacy principles (e.g. Privacy Model).

With this evolution, fundamental assumptions of the web platform are being redefined or removed. Access to cookies in a third-party context are one of those assumptions. While overall good for the web, the third-party cookie deprecation removes a fundamental building block used by certain designs of federated identity.

The Federated Credential Management API aims to bridge the gap for the federated identity designs which relied on third-party cookies. The API provides the primitives needed to support federated identity when/where it depends on third-party cookies, from sign-in to sign-out and revocation.

In order to provide the federated identity primitives without the use of third-party cookies the API places the user agent as a mediator between a RP (website that requests user information for federated sign in) and an IDP (website that provides user information for federated sign in). This mediation requires user permission before allowing the RPs and IDPs to know about their connection to the user.

The specification leans heavily on changes in the user agent and IDP and minimally on the RP. The FedCM API provides a way to authenticate and fetch tokens.

Example showing how a website allowing for a single logged in account could be implemented.
<html>
<head>
  <title>Welcome to my Website</title>
</head>
<body>
  <button onclick="login()">Login with idp.example</button>

  <script>
  let nonce;
  async function login() {
    // Assume we have a method returning a random number. Store the value in a variable which can
    // later be used to check against the value in the token returned.
    nonce = random();
    // Prompt the user to select an account from the IDP to use for
    // federated login within the RP. If resolved successfully, the Promise
    // returns an IdentityCredential object from which the <code data-opaque bs-autolink-syntax='`token`'>token</code> can be
    // extracted.
    return await navigator.credentials.get({
      identity: {
        providers: [{
          configURL: "https://idp.example/manifest.json",
          clientId: "123",
          nonce: nonce
        }]
      }
    });
  }
  </script>
</body>
</html>

At a high level, the Federated Credential Management API works by the intermediation of cooperating IDPs and RPs.

The § 3 Identity Provider HTTP API defines a set of HTTP APIs that cooperating IDPs and IDPs exposes as well as the entry points in the § 2 The Browser API that they can use.

The user agent intermediates in such a matter that makes it impractical for the API to be used for tracking purposes, while preserving the functionality of identity federation.

2. The Browser API

The Browser API exposes APIs to RPs and IDPs to call and intermediates the exchange of the user’s identity.

The Sign-up and Sign-in APIs are used by the RPs to ask the browser to intermediate the relationship with the IDP and the provisioning of a token.

NOTE: The RP makes no delineation between Sign-up and Sign-in, but rather calls the same API indistinguishably.

If all goes well, the Relying Party receives back an IdentityCredential which contains a token it can use to authenticate the user.

const credential = await navigator.credentials.get({
  identity: {
    providers: [{
      configURL: "https://idp.example/manifest.json",
      clientId: "123",
    }]
  }
});

For fetches that are sent with cookies we include unpartitioned cookies, as if the resource was loaded as a same-origin request, e.g. regardless of the SameSite value (which is used when a resource loaded as a third-party, not first-party). This makes it easy for an IDP to adopt the FedCM API without introducing security issues on the API, since the RP cannot inspect the results from the fetches in any way.

2.1. The State Machine

Each user agent keeps track of a global state machine map, an initially empty map. The keys in the state machine map are triples of the form (rp, idp, account) where rp is the origin of the RP, idp is the origin of the IDP, and account is a string representing an account identifier. The values in the state machine map are AccountState objects which have the following properties:

registration state

Keeps track of whether the user agent is aware that the user has registered an account on the RP with the IDP or not. Can be registered or unregistered (by default).

allows logout

Boolean which keeps track of whether the user agent would allow the account to be logged out via logoutRPs(). It is initialized to false by default. Note that this value being true does not imply that the user is logged in to the RP with the account, it merely implies that logoutRPs() has not yet been called on this account after the last successful IdentityCredential creation with this account.

To compute account state given an IdentityProviderConfig provider, an IdentityProviderAccount account, and a globalObject, run the following steps. This returns an AccountState.
  1. Let configUrl be the result of running parse url with provider’s configURL and globalObject.

  2. Let idpOrigin be the origin corresponding to configUrl.

  3. Let rpOrigin be globalObject’s associated Document's origin.

  4. Let accountId be account’s id.

  5. Let triple be (rpOrigin, idpOrigin, accountId).

  6. If state machine map[triple] does not exist, set state machine map[triple] to a new AccountState.

  7. Let accountState be state machine map[triple].

  8. Return accountState.

To sign-in the user with a given an AccountState accountState:
  1. Assert that accountState’s registration state is registered

  2. Change accountState’s allows logout from false to true.

TODO: add an ASCII image to explain how the states fit into the algorithms.

2.2. The IdentityCredential Interface

This specification introduces a new type of Credential, called an IdentityCredential:

[Exposed=Window, SecureContext]
interface IdentityCredential : Credential {
  readonly attribute USVString? token;
};
token

The token's attribute getter returns the value it is set to. It represents the minted token provided by the IDP.

The main entrypoint in this specification is through the entrypoints exposed by the Credential Management API.

2.2.1. The CredentialRequestOptions

This specification starts by introducing an extension to the CredentialRequestOptions object:

partial dictionary CredentialRequestOptions {
  IdentityCredentialRequestOptions identity;
};

The IdentityCredentialRequestOptions contains a list of IdentityProviderConfigs that the RP supports and has pre-registered with (i.e. it has a clientId).

dictionary IdentityCredentialRequestOptions {
  sequence<IdentityProviderConfig> providers;
};

Each IdentityProviderConfig represents an IDP that the RP supports (e.g. that it has a pre-registration agreement with).

dictionary IdentityProviderConfig {
  required USVString configURL;
  required USVString clientId;
  USVString nonce;
};
configURL

The URL of the configuration file for the identity provider.

clientId

The client_id provided to the RP out of band by the IDP

nonce

A random number of the choice of the RP. It is generally used to associate a client session with a token and to mitigate replay attacks. Therefore, this value should have sufficient entropy such that it would be hard to guess.

2.2.2. The [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) internal method

The [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) algorithm runs in parallel inside the Credential Management § algorithm-request to request credentials and returns a set of IdentityCredential for the requested IDPs.

This internal method accepts three arguments:

origin

This argument is the relevant settings object's origin, as determined by the calling get() implementation, i.e., CredentialsContainer's Request a Credential abstract operation.

options

This argument is a CredentialRequestOptions object whose identity member contains an IdentityCredentialRequestOptions object specifying the exchange options.

sameOriginWithAncestors

This argument is a Boolean value which is true if and only if the caller’s environment settings object is same-origin with its ancestors. It is false if caller is cross-origin.

Note: Invocation of this internal method indicates that it was allowed by permissions policy, which is evaluated at the Credential Management Level 1 level. See § 4 Permissions Policy Integration. As such, sameOriginWithAncestors is unused.

NOTE: The mediation flag is currently not used.

The signal is used as an abort signal for the requests.

When the IdentityCredential's [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) algorithm is invoked, the user agent MUST execute the following steps. This returns an IdentityCredential (or throws an error to the caller).
  1. Assert: These steps are running in parallel.

  2. Assert: options["identity"]["providers"] exists.

  3. Assert: options["identity"]["providers"] size is 1.

    Support choosing accounts from multiple IDPs, as described here.

  4. Run setTimeout() passing a task which throws a NetworkError, after a timeout of 60 seconds.

    Do not use setTimeout() directly, as that is not correct. See the relevant issue.

    Note: the purpose of having a timer here is to avoid leaking the reason causing this method to throw an error. If there was no such timer, the developer could easily infer whether the user has an account with the IDP or not, or whether the user closed the UI without granting permission to share the IDP account information with the RP.

  5. Let provider be options["identity"]["providers"][0].

  6. Let credential be the result of running create an IdentityCredential with provider and globalObject.

    Note: The globalObject is not currently passed onto the [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) algorithm. See issue.

  7. If credential is failure, queue a global task on the DOM manipulation task source to throw a new "NetworkError" DOMException.

To create an IdentityCredential given an IdentityProviderConfig provider and a globalObject, run the following steps. This returns an IdentityCredential or failure.
  1. Assert: These steps are running in parallel.

  2. Let config be the result of running fetch the config file with provider and globalObject.

  3. If config is failure, return failure.

  4. Let accountsList be the result of fetch the accounts list with config, provider, and globalObject.

  5. For each account in accountsList:

    1. If account["picture"] is present, fetch the account picture with account and globalObject.

    Note: The user agent may choose to show UI which does not initially require fetching the account pictures. In these cases, the user agent may delay these fetches until they are needed.

  6. If accountsList’s size is 1:

    1. Let account be accountsList[0].

    2. Let accountState be the result of running the compute account state algorithm given provider, account, and globalObject.

    3. If accountState’s registration state is unregistered, let permission be the result of running request permission to sign-up algorithm with account, accountState, config, provider, and globalObject.

    4. Otherwise, show a dialog to request user permission to sign in via account, and set the result in permission.

    5. If permission, sign-in with accountState.

  7. Otherwise:

    1. Let account be the result of running the select an account from the accountsList.

    2. If account is failure, return failure.

    3. Let accountState be the result of running the compute account state algorithm given provider and account.

    4. If accountState’s registration state is unregistered:

      1. Let permission be the result of running the request permission to sign-up algorithm with account, accountState, config, provider, and globalObject.

      2. If permission, sign-in with accountState.

    5. Otherwise, sign-in with accountState.

  8. Wait until the user agent's dialog is closed.

  9. If accountState’s registration state is unregistered then return failure.

  10. Let credential be the result of running the fetch an identity assertion algorithm with accountState, account’s id, provider, config, and globalObject.

  11. Return credential.

To parse url given a USVString stringUrl, a globalObject, and an optional baseUrl (default null), run the following steps. This returns a URL or failure.
  1. Let configUrl be null.

  2. Queue a global task on the DOM manipulation task source given globalObject to set configUrl to the result of running url parser with stringUrl and baseUrl.

    Note: We queue a task since the url parser needs to be run within a task, not in parallel.

  3. Wait for configUrl to be set.

  4. Return configUrl.

To fetch request given a request request, globalObject, and an algorithm processResponseConsumeBody, run the following steps:
  1. Queue a global task on the network task source given globalObject to:

    1. Fetch request with processResponseConsumeBody set to processResponseConsumeBody.

Note: We queue a task since the fetch needs to be run within a task, not in parallel.

To fetch the config file given an IdentityProviderConfig provider and globalObject, run the following steps. This returns an IdentityProviderAPIConfig.
  1. Let configUrl be the result of running parse url with provider’s configURL and globalObject.

  2. If configUrl is failure, return failure.

  3. Run a Content Security Policy Level 3 check with a connect-src directive on the URL passed as configUrl. If it fails, return failure.

  4. If configUrl is not a potentially trustworthy URL, return failure.

  5. Let rootUrl be a new URL.

  6. Set rootUrl’s scheme to configUrl’s scheme.

  7. Set rootUrl’s host to configUrl’s host's registrable domain.

  8. Set rootUrl’s path to the list «".well-known", "web-identity"».

  9. If rootUrl is not a potentially trustworthy URL, return failure.

  10. Let wellKnownRequest be a new request as follows:

    URL

    rootUrl

    client

    null

    window

    "no-window"

    service-workers mode

    "none"

    destination

    "webidentity"

    origin

    a unique opaque origin

    header list

    a list containing a single header with name set to Accept and value set to application/json

    referrer policy

    "no-referrer"

    credentials mode

    "omit"

    The spec is yet to be updated so that all requests are created with mode set to "user-agent-no-cors". See the relevant pull request for details.

  11. Let config be null.

  12. Fetch request with wellKnownRequest, globalObject, and processResponseConsumeBody set to the following steps given a response response and responseBody:

    1. Let json be the result of extract the JSON fetch response from response and responseBody.

    2. Convert json to an IdentityProviderWellKnown, discovery.

    3. If the size of discovery["provider_urls"] is greater than 1, set config to failure.

      relax the size of the provider_urls array.

    4. If discovery["provider_urls"][0] is NOT equal to provider’s configURL, set config to failure.

  13. Let configRequest be a new request as follows:

    url

    configUrl

    redirect mode

    "error"

    client

    null

    window

    "no-window"

    service-workers mode

    "none"

    destination

    "webidentity"

    origin

    a unique opaque origin

    header list

    a list containing a single header with name set to Accept and value set to application/json

    referrer policy

    "no-referrer"

    credentials mode

    "omit"

  14. Fetch request with configRequest, globalObject, and processResponseConsumeBody set to the following steps given a response response and responseBody:

    1. Let json be the result of extract the JSON fetch response from response and responseBody.

    2. Convert json to an IdentityProviderAPIConfig stored in config, unless config has been set to failure.

  15. Wait for both fetch responses to be completed.

  16. Return config.

NOTE: We use a two-tier file system in order to prevent the IDP to easily determine the RP that a user is visiting by encoding the information in the config file path. We solve this issue by requiring a well-known file to be on the root of the IDP. The config file itself can be anywhere, but it will not be used if the user agent does not find it in the well-known file. This allows the IDP to keep their actual config files on an arbitary path while allowing the user agent to prevent config file path manipulation to fingerprint (for instance, by including the RP in the path). See § 6.3.1 Manifest Fingerprinting.

dictionary IdentityProviderWellKnown {
  required sequence<USVString> provider_urls;
};

dictionary IdentityProviderIcon {
  required USVString url;
  unsigned long size;
};

dictionary IdentityProviderBranding {
  USVString background_color;
  USVString color;
  sequence<IdentityProviderIcon> icons;
  USVString name;
};

dictionary IdentityProviderAPIConfig {
  required USVString accounts_endpoint;
  required USVString client_metadata_endpoint;
  required USVString id_assertion_endpoint;
  IdentityProviderBranding branding;
};
To fetch the accounts list given an IdentityProviderAPIConfig config, an IdentityProviderConfig provider, and globalObject, run the following steps. This returns an IdentityProviderAccountList.
  1. Let accountsUrl be the result of computing the manifest URL given provider, config["accounts_endpoint"], and globalObject.

  2. If accountsUrl is failure, return an empty list.

  3. Let request be a new request as follows:

    url

    accountsUrl

    redirect mode

    "error"

    client

    null

    window

    "no-window"

    service-workers mode

    "none"

    destination

    "webidentity"

    origin

    a unique opaque origin

    header list

    a list containing a single header with name set to Accept and value set to application/json

    referrer policy

    "no-referrer"

    credentials mode

    "include"

    The credentialed fetch in this algorithm can lead to a timing attack that leaks the user’s identities before the user grants permission. This is an active area of investigation that is being explored here.

  4. Let accountsList be null.

  5. Fetch request with request, globalObject, and processResponseConsumeBody set to the following steps given a response response and responseBody:

    1. Let json be the result of extract the JSON fetch response from response and responseBody.

    2. Convert json to an IdentityProviderAccountList, and store the result in accountsList.

    We should validate the accounts list returned here for repeated ids, as described here.

  6. Wait for accountsList to be set.

  7. Return accountsList.

dictionary IdentityProviderAccount {
  required USVString id;
  required USVString name;
  required USVString email;
  USVString given_name;
  USVString picture;
  sequence<USVString> approved_clients;
};
dictionary IdentityProviderAccountList {
  sequence<IdentityProviderAccount> accounts;
};
To fetch the account picture given an IdentityProviderAccount account and a globalObject, run the following steps:
  1. Let pictureRequest be a new request as follows:

    url

    account["picture"]

    client

    null

    window

    "no-window"

    service-workers mode

    "none"

    destination

    "image"

    origin

    a unique opaque origin

    referrer policy

    "no-referrer"

    credentials mode

    "omit"

  2. Fetch request with pictureRequest, globalObject, and processResponseConsumeBody set to the following steps given a response and a responseBody:

    1. If responseBody is null or failure, the user agent may choose an arbitrary placeholder image and associate it with the account.

    2. Otherwise, decode responseBody into an image, and associate it with account if successful. This allows the user agent to use the decoded image in a dialog displaying account.

To fetch an identity assertion given an AccountState accountState, a USVString accountId, an IdentityProviderConfig provider, an IdentityProviderAPIConfig config, and globalObject, run the following steps. This returns an IdentityCredential.
  1. Assert accountState’s registration state is registered.

  2. Assert accountState’s allows logout is true.

  3. Let idTokenUrl be the result of computing the manifest URL given provider, config["id_assertion_endpoint"], and globalObject.

  4. If idTokenUrl is failure, return failure.

  5. Let requestBody be a string resulting in concatenating "client_id=", provider’s clientId, "&nonce=", provider’s nonce, "&account_id=", and accountId.

  6. Let request be a new request as follows:

    url

    idTokenUrl

    mode

    "POST"

    body

    the UTF-8 encode of requestBody

    redirect mode

    "error"

    client

    null

    window

    "no-window"

    service-workers mode

    "none"

    destination

    "webidentity"

    origin

    RP's origin (TODO)

    header list

    a list containing a single header with name set to Accept and value set to application/x-www-form-urlencoded

    credentials mode

    "include"

  7. Let credential be null.

  8. Fetch request with request, globalObject, and processResponseConsumeBody set to the following steps given a response response and responseBody:

    1. Let json be the result of extract the JSON fetch response from response and responseBody.

    2. Convert json to an IdentityProviderToken, token.

    3. Let credential be a new IdentityCredential given globalObject’s realm.

    4. Set credential’s token to token.

  9. Wait for credential to be set.

  10. Return credential.

dictionary IdentityProviderToken {
  required USVString token;
};
When computing the manifest URL given an IdentityProviderConfig provider, a string manifestString, and globalObject, perform the following steps. This returns a URL or failure.
  1. Let configUrl be the result of running parse url with provider’s configURL and globalObject.

  2. Let manifestUrl be the result of running parse url given manifestString and globalObject.

  3. If manifestUrl is failure, let manifestUrl be the result of running parse url given manifestString (the relative URL), globalObject, and configUrl (the base URL). Wait until manifestUrl is set again.

    Note: This means the we allow passing the manifest string as either an absolute or relative URL.

  4. If manifestUrl is failure, return failure.

  5. If manifestUrl is not same origin with configUrl, return failure.

  6. If manifestUrl is not a potentially trustworthy URL, return failure.

  7. Return manifestUrl.

To extract the JSON fetch response given a response response and a responseBody, run the following steps. This returns an ordered map.
  1. Assert: These steps are running on the networking task source.

  2. If response is a network error or its status is not an ok status, throw a new "NetworkError" DOMException.

  3. Let mimeType be the result of extracting a MIME TYPE from response’s header list.

  4. If mimeType is failure or is not a JSON MIME Type, throw a new "NetworkError" DOMException.

  5. Let json be the result of parse JSON bytes to an Infra value passing responseBody.

  6. If json is a parsing exception, or if json is not an ordered map, throw a new "NetworkError" DOMException.

  7. Return json.

To request permission to sign-up the user with a given an IdentityProviderAccount account, an AccountState accountState, an IdentityProviderAPIConfig config, an IdentityProviderConfig provider, and a globalObject, run the following steps. This returns a boolean.
  1. Assert: These steps are running in parallel.

  2. Let metadata be the result of running fetch the client metadata with config, provider, and globalObject.

  3. If metadata is not null, metadata["privacy_policy_url"] is defined and the provider’s clientId is not in the list of account["approved_clients"], then display the metadata["privacy_policy_url"] link.

  4. If metadata is not null, metadata["terms_of_service_url"] is defined, and the provider’s clientId is not in the list of account["approved_clients"], then display the metadata["terms_of_service_url"] link.

  5. Prompt the user to gather explicit intent to create an account. The user agent MAY use the IdentityProviderBranding to inform the style choices of its UI.

  6. If the user does not grant permission, return false.

  7. Change accountState’s registration state from unregistered to registered.

  8. Change accountState’s allows logout from false to true.

  9. Return true.

To fetch the client metadata given an IdentityProviderAPIConfig config and an IdentityProviderConfig provider, run the following steps. This returns an IdentityProviderClientMetadata or null.
  1. Let clientMetadataUrl be the result of computing the manifest URL given provider, config["client_metadata_endpoint"], and globalObject.

  2. If clientMetadataUrl is failure, return null.

  3. Let request be a new request as follows:

    url

    clientMetadataUrl

    redirect mode

    "error"

    client

    null

    window

    "no-window"

    service-workers mode

    "none"

    destination

    "webidentity"

    origin

    RP's origin (TODO)

    header list

    a list containing a single header with name set to Accept and value set to application/json

    credentials mode

    "omit"

  4. Let metadata be null.

  5. Fetch request with request, globalObject, and processResponseConsumeBody set to the following steps given a response response and responseBody:

    1. Let json be the result of extract the JSON fetch response from response and responseBody.

    2. Convert json to an IdentityProviderClientMetadata, and store the result in metadata.

  6. Wait until metadata is set.

  7. Return metadata.

dictionary IdentityProviderClientMetadata {
  USVString privacy_policy_url;
  USVString terms_of_service_url;
};
To select an account given an accountsList, run the following steps. This returns an IdentityProviderAccount or failure.
  1. Assert accountsList’s size is greater than 1.

  2. Display an account chooser displaying the options from accountsList.

  3. Let account be the IdentityProviderAccount of the account that the user manually selects from the accounts chooser, or failure if no account is selected.

  4. Return account.

3. Identity Provider HTTP API

This section is non-normative.

The IDP proactively and cooperatively exposes itself as a comformant agent by exposing a series of HTTP endpoints:

  1. § 3.1 The Well-Known File that points to a Manifest

  2. A § 3.2 Manifest endpoint in an agreed upon location that points to

  3. An § 3.3 Accounts List endpoint

  4. A § 3.4 Client Metadata endpoint

  5. An § 3.5 Identity Assertions endpoint

The FedCM API introduces the ability for a site to ask the browser to execute a few different network requests. It is important for the browser to execute these in such a way that it does not allow the user to be tracked (by an attacker impersonating an IDP) on to the site using FedCM. The following table has information about the network requests performed:

3.1. The Well-Known File

NOTE: The browser uses the well-known file to prevent the following attack described here.

The IDP exposes a well-known file in a pre-defined location, specifically at the "web-identity" file at the IDPs's path ".well-known".

The well-known file is fetched:

(a) without cookies, (b) with the Sec-Fetch-Dest header set to webidentity, and (c) without revealing the RP in the Origin or Referer headers.

For example:

GET /.well-known/web-identity HTTP/1.1
Host: idp.example
Accept: application/json
Sec-Fetch-Dest: webidentity

The file is parsed expecting a IdentityProviderWellKnown JSON object.

The IdentityProviderWellKnown JSON object has the following semantics:

provider_urls (required), of type sequence<USVString>

A list of URLs that points to valid § 3.2 Manifests.

3.2. Manifest

The config endpoint is an endpoint which serves as a discovery device to other endpoints provided by the IDP.

The config endpoint is fetched:

(a) without cookies, (b) with the Sec-Fetch-Dest header set to webidentity, (c) without revealing the RP in the Origin or Referer headers, and (c) without following HTTP redirects.

For example:

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

The response body must be a JSON object that can be converted to an IdentityProviderAPIConfig without an exception.

The IdentityProviderAPIConfig object’s members have the following semantics:

accounts_endpoint, of type USVString

A URL that points to an HTTP API that complies with the § 3.3 Accounts List API.

client_metadata_endpoint, of type USVString

A URL that points to an HTTP API that complies with the § 3.4 Client Metadata API.

id_assertion_endpoint, of type USVString

A URL that points to an HTTP API that complies with the § 3.5 Identity Assertions API.

branding, of type IdentityProviderBranding

A set of IdentityProviderBranding options.

The IdentityProviderBranding enables an IDP to express their branding preferences, which may be used by user agents to customize the permission prompt.

Note: The branding preferences are deliberately designed to be high level / abstract (rather than opinionated about a specific UI structure), to enable different user agents to offer different UI experiences and for them to evolve independently over time.

Its members have the following semantics:

background_color, of type USVString

Background color for IDP-branded widgets such as buttons.

color, of type USVString

color for text on IDP branded widgets.

icons, of type sequence<IdentityProviderIcon>

A list of IdentityProviderIcon objects.

name, of type USVString

A user-recognizable name for the IDP.

Note: The branding preferences are deliberately designed to be high level / abstract (rather than opinionated about a specific UI structure), to enable different user agents to offer different UI experiences and for them to evolve independently over time.

The IdentityProviderIcon has members with the following semantics:

url, of type USVString

The url pointing to the icon image, which must be square and single resolution (not a multi-resolution .ico). The icon needs to comply with the maskable specification.

size, of type unsigned long

The width/height of the square icon. The size may be omitted if the icon is in a vector graphic format (like SVG).

Note: the user agent reserves a square size for the icons provided by the developer. If the developer provides an icon that is not square, the user agent may choose to not display it at all, trim the icon and show a square portion of it, or even transform it into a square icon and show that.

The color is a subset of CSS <color> syntax, namely <hex-color>s, hsl()s, rgb()s and <named-color>.

For example:

{
  "accounts_endpoint": "/accounts",
  "client_metadata_endpoint": "/metadata",
  "id_assertion_endpoint": "/assertion",
  "branding": {
    "background_color": "green",
    "color": "0xFFEEAA",
    "icons": [{
      "url": "https://idp.example/icon.ico",
      "size": 25
    }],
    "name": "IDP Example"
  }
}

3.3. Accounts List

The accounts list endpoint provides the list of accounts the user has at the IDP.

The accounts list endpoint is fetched (a) with IDP cookies, (b) with the Sec-Fetch-Dest header set to webidentity, (c) without revealing the RP in the Origin or Referer headers, and (d) without following HTTP redirects.

For example:

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

The response body must be a JSON object that can be converted to an IdentityProviderAccountList without an exception.

Every IdentityProviderAccount is expected to have members with the following semantics:

id, of type USVString

The account unique identifier.

name, of type USVString

The user’s full name.

email, of type USVString

The user’s email address.

given_name, of type USVString

The user’s given name.

picture, of type USVString

URL for the account’s picture.

approved_clients, of type sequence<USVString>

A list of RPs (that gets matched against the requesting clientId) this account is already registered with. Used in the request permission to sign-up to allow the IDP to control whether to show the Privacy Policy and the Terms of Service.

For example:

{
 "accounts": [{
   "id": "1234",
   "given_name": "John",
   "name": "John Doe",
   "email": "john_doe@idp.example",
   "picture": "https://idp.example/profile/123",
   "approved_clients": ["123", "456", "789"]
  }, {
   "id": "5678",
   "given_name": "Johnny",
   "name": "Johnny",
   "email": "johnny@idp.example",
   "picture": "https://idp.example/profile/456"
   "approved_clients": ["abc", "def", "ghi"]
  }]
}

Clarify the IDP API response when the user is not signed in.

3.4. Client Metadata

The client metadata endpoint provides metadata about RPs.

The client medata endpoint is fetched (a) without cookies, (b) with the Sec-Fetch-Dest header set to webidentity, (c) with the RP's origin in the Origin header, and (d) without following HTTP redirects.

The user agent also passes the client_id.

For example:

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

The response body must be a JSON object that can be converted to an IdentityProviderClientMetadata without an exception.

The IdentityProviderClientMetadata object’s members have the following semantics:

privacy_policy_url, of type USVString

A link to the RP's Privacy Policy.

terms_of_service_url, of type USVString

A link to the RP's Terms of Service.

For example:

{
  "privacy_policy_url": "https://rp.example/clientmetadata/privacy_policy.html",
  "terms_of_service_url": "https://rp.example/clientmetadata/terms_of_service.html"
}

3.5. Identity Assertions

The identity assertion endpoint is responsible for minting a new token that contains signed assertions about the user.

The identity assertion endpoint is fetched

(a) as a POST request, (b) with IDP cookies, (c) with the RP's origin in the Origin header, and (d) with the Sec-Fetch-Dest header set to webidentity, (e) without following HTTP redirects.

It will also contain the following parameters in the request body application/x-www-form-urlencoded:

client_id

The RP's unique identifier from the IDP

nonce

The request nonce

account_id

The account identifier that was selected.

disclosure_text_shown

Whether the user agent has explicitly shown to the user what specific information the IDP intends to share with the RP (e.g. "idp.example will share your name, email... with rp.example"), used by the request permission to sign-up algorithm for new users but not by the sign-in algorithm for returning users.

For example:

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=Ct60bD&disclosure_text_shown=true
An IDP MUST check the Origin header to ensure that a malicious RP does not receive an ID token corresponding to another RP. In other words, the IDP MUST check that the Origin header value is represented by the clientId. As the clientId are IDP-specific, the user agent cannot perform this check.

Note: An IDP should validate the nonce, if present, to prevent CSRF-style attacks.

The response body must be a JSON object that can be converted to an IdentityProviderToken without an exception.

Every IdentityProviderToken is expected to have members with the following semantics:

token, of type USVString

The resulting token.

The content of the token is opaque to the user agent and can contain anything that the IDP would like to pass to the RP to facilitate the login. For this reason the RP is expected to be the party responsible for validating the token passed along from the IDP using the appropriate token validation algorithms defined. One example of how this might be done is defined in OIDC Connect Core § IDTokenValidation.

NOTE: For IDPs, it is worth considering how portable accounts are. Portability is left entirely up to IDPs, who can choose between a variety of different mechanisms to accomplish it (e.g. OIDC’s Account Porting).

For example:

{
  "token" : "eyJC...J9.eyJzdWTE2...MjM5MDIyfQ.SflV_adQssw....5c"
}

3.6. Backwards Compatibility

Note: go over how we are planning to deal with backwards compatibility.

4. Permissions Policy Integration

FedCM defines a policy-controlled feature identified by the string "identity-credentials-get". Its default allowlist is "self".

A Document’s permissions policy determines whether any content in that document is allowed to obtain a credential object using the Browser API. Attempting to invoke navigator.credentials.get({identity:..., ...}) in documents that are not allowed to use the identity-credentials-get feature will result in a promise rejected with a "NotAllowedError" DOMException.

This restriction can be controlled using the mechanisms described in Permissions Policy.

Note: Algorithms specified in Credential Management Level 1 perform the actual permissions policy evaluation. This is because such policy evaluation needs to occur when there is access to the current settings object. The internal methods modified by this specification do not have such access since they are invoked in parallel by CredentialsContainer's Request a Credential abstract operation.

5. Security

This section provides a few of the security considerations for the FedCM API. Note that there is a separate section for § 6 Privacy considerations.

5.1. Content Security Policy

The first fetches triggered by the FedCM API are the manifest list, which is public, and the config file. Imagine a malicious script included by (and running as) the RP attempting to execute the FedCM API calls to a malicious IDP, one which is not trusted by the RP. If the call is successful, this would introduce browser UI on the RP with sign in options into a malicious IDP. This maliciousIDP could then attempt to trick the user. The protection against this attack is the Content Security Policy Level 3 check, which would fail because the origin of the manifest of the malicious IDP would not be an origin included in the allowlist specified by the Content Security Policy Level 3 of the RP, hence preventing the undesired FedCM UI from being shown. Since any subsequent fetches are same origin with respect to the config file or at least dependent on the contents of the config file, they do not require additional checks.

The non-same-origin fetches include, for example, the brand icon. The user agent does not perform a Content Security Policy Level 3 check on these because they are directly specified from the manifest. In addition, the rendering of this image is performed by the user agent, and as such this image cannot affect the RP site nor can they be inspected by the RP in any way.

5.2. Sec-Fetch-Dest Header

This section is non-normative.

The FedCM API introduces several non-static endpoints on the IDP, so these need to be protected from XSS attacks. In order to do so, the FedCM API introduces a new value for the Sec-Fetch-Dest header, a forbidden request-header. The requests initiated by the FedCM API have a webidentity value for this header. The value cannot be set by random websites, so the IDP can be confident that the request was originated by the FedCM browser rather than sent by a websites trying to run an XSS attack. An IDP must to check for this header’s value in the credentialed requests it receives, which ensures that the request was initiated by the user agent, based on the FedCM API. A malicious actor cannot spam FedCM API calls, so this is sufficient protection for the new IDP endpoints.

5.3. Browser Surface Impersonation

The FedCM API introduces new (trusted) user agent UI, and the user agent may choose to show the UI entirely on top of the page’s contents, if anything because the page was the one responsible for this UI. This introduces a potential concern of a malicious site to try to replicate the FedCM UI, gain the user’s trust that the user is interacting with a trusted browser surface, and gather information from the user that they would only give to the browser rather than the site (e.g. usernames/passwords of another site). This would be hard to achieve because the FedCM UI uses metadata about the user accounts of the IDP, which the malicious website doesn’t have access to. If this is a malicious site, it would not know the user accounts unless the user is already compromised. However, the site could have some guess of the user identity, so the browser is encouraged to provide UI that is hard to replicate and that clearly presents the domains of the parties involved in the website’s FedCM call. Overall, an attacker trying to impersonate the browser using exclusively UI that is accessible to the content area (e.g. iframes) to attempt to retrieve sensitive information from the user would be noticeably different from the FedCM UI. Finally, because the FedCM UI can only be queried from the top-level frame (or potentially from an iframe with explicit permission from the top-level frame), the priviledged UI surface is only shown when the top-level frame wants it so. A sneaky iframe cannot force the FedCM UI to occlude important content from the main page.

6. Privacy

This section is intended to provide a comprehensive overview of the privacy risks associated with federated identity on the web for the purpose of measuring the privacy risks and benefits of proposed browser intermediation designs.

6.1. Principals

This section describes the four principals that would participate in an invocation of the API and expectations around their behavior.

  1. The user agent implements § 2 The Browser API and controls the execution contexts for the RP and IDP content. The user agent is assumed to be trusted by the user, and transitively trusted by the RP and IDP.

  2. RPs are websites that invoke the FedCM API for the purpose of authenticating a user to their account or for requesting information about that user. Since any site can invoke the API, RPs cannot necessarily be trusted to limit the user information it collects or use that information in an acceptable way.

  3. IDPs are third-party websites that are the target of a FedCM call to attempt to fetch a token. Usually,the IDP has a higher level of trust than the RP since it already has the user’s personal information, but it is possible that the IDP might use the user’s information in non-approved ways. In addition, it is possible that the IDP specified in the API call may not be an IDP the user knows about. In this case, it likely does not have personal user information in advance.

  4. A tracker is a third-party website that is not an IDP but could abuse the FedCM API exclusively for the purpose of tracking a user as they visit various websites. A tracker may be injected by the RP with or without their knowledge (e.g. injected into one of the various script tags that the RP embeds that is loaded dynamically), but they usually do not modify the UI of the website, so that it is harder to detect the tracking. A tracker that successfully adds tracking scripts for users in various websites may then use this information for various purposes, such as selling the information about the user. It should not be possible for trackers to use the FedCM API to track users across the web.

Based on the above, the privacy discussion makes the following assumptions:

  1. It is not acceptable for an RP, IDP, or tracker to learn that a specific user visited a specific site, without any permission granted by the user. That is, the user agent needs to hide the knowledge that a specific user identity visited a specific site from the RP, IDP, and trackers. It may share this knowledge with the RP and IDP once the user grants permission. In particular, the RP should not know the user identity and the IDP should not know the site that the user has visited before the FedCM flow gathers the user’s permission to do so.

  2. It is the user agent's responsibility to determine when the user has granted permission for the IDP to communicate with the RP in order to provide identification for the user.

  3. Once the user agent has determined that the user has granted permission to provide their account information to the RP, it is ok for the IDP and for the RP to learn information about that specific user’s account, as required to provide identification. The RP should not learn about about any other user accounts.

6.2. Network requests

We want to ensure that the FedCM fetches are all same-origin with respect to the provider specified by the RP. The reason for this is because fetches with cookies would use the cookies from the origin specified, so allowing arbitrary origins would introduce confusion and potential privacy issues with regards to which cookies are shared and with whom within the FedCM flow. The easiest way to ensure that all of these fetches remain same-origin is by disabling redirects and checking the origin of the fetched URLs.

6.3. Attack Scenarios

This section describes the scenarios in which various agents might attempt to gain user information. It considers the possibilities when:

For the purposes of this section, a principal is considered to be participating in the collection of information if it directly or indirectly performs actions with the aim of realizing one of the above threats.

Note: An example of indirect collusion would be an RP importing a script supplied by an IDP where the IDP intends to track users.

For the purpose of discussion, this document assumes that third-party cookies are disabled by default and are no longer effective for use in tracking mechanisms, and also some form of mitigations are implemented against ‘bounce tracking’ using link decoration or postMessage. Most of these scenarios consider how user tracking might happen without them. See also Pervasive Monitoring Is an Attack.

6.3.1. Manifest Fingerprinting

Suppose that the FedCM API did not have a two-tier manifest (see the create an IdentityCredential algorithm), and instead directly had a single manifest. This would introduce the following fingerprinting attack:

// The following will fetch the manifest JSON file, which will know the origin of the RP :(
const cred = await navigator.credentials.get({
  identity: {
    providers: [{
      configURL: `https://idp.example/${window.location.href}`
    }]
  }
});

NOTE: You can read more about the attack described here.

Here, the RP includes identifies itself when fetching the manifest from the IDP. This coupled with the credentialed fetches that the FedCM API performs would enable the IDP to easily track the website that the user is visiting, without requiring any permission from the user. Our mitigation to this problem is to use the § 3.1 The Well-Known File file. We enforce a specifically named file at the root of the IDP's domain to ensure that the file name does not introduce fingerprints about the RP being visited.

The whole manifest could be in this location, but we instead choose to only point to the real manifest from there. This allows us to have flexibility in the future to allow a small constant number of manifests, should an IDP require this in the future, instead of just a single one. It also helps the IDP's implementation because they any changes to the manifest are more likely to be performed on a file located anywhere, as opposed to the root of the domain, which may have more constraints in terms of ease of update.

6.3.2. Timing Attacks

In the timing attack, the RP and IDP collude to allow the IDP to compute the (RP's origin, IDP's user identity) pair without the user’s permission. This attack is not deterministic: there is room for statistical error because it requires stitching together two separate pieces of information to track the user. However, it is important to mitigate this attack and ensure that it’s economically impractical to perform. In this attack, we assume that network requests do not have large fingerprinting vectors (e.g. IP addresses). These vary by user agent and are hard to eliminate entirely, but in general user agents are expected to address these over time. These bits of information tied to the network requests make the timing attack easier, making it more important to address.

Note: this attack is described and discussed here.

The attack is as follows:

  1. The RP logs the time at which it invokes the API, time A and sends it to the IDP to learn itself by marking the time in which it receives the fetch for the RP's client metadata. Time A is tied to the site.

  2. A credentialed request is sent to the IDP that does not explicitly identify the RP, but it is sent around a time that is close enough to the request above. The IDP notes the time in which this request has arrived, time B. Time B is tied to the user identity.

  3. The IDP correlates time A and time B to find the (site, user identity) pair with high probability. Note that fingerprinting can make the correlation more accurate.

Note that this kind of correlation is already possible without FedCM by using simple cross-origin top-level navigations, but using FedCM for this purpose would worsen the problem if it improved timing resolution or if it was less visible to users (e.g. the IDP could return empty accounts to the user agent to deliberately make no browser UI to be triggered, and hence make this attack invisible to the user).

The user agent should mitigate this attack to protect users, in an interoperable way.

6.3.3. IDP Intrusion

From Target Privacy Threat Model § hl-intrusion

Privacy harms don’t always come from a site learning things.

From RFC6973: Intrusion

Intrusion consists of invasive acts that disturb or interrupt one’s life or activities. Intrusion can thwart individuals' desires to be left alone, sap their time or attention, or interrupt their activities.

In the context of federation, intrusion happens when an RP and an IDP are colluding to invasively and aggressively recommend the user to login disproportionally to the their intent. Much like unsolicited notifications, an RP can collude with an IDP to aggressively log users in.

The user agent can mitigate this by mediating the user controls and offering them proportionally to the intent of the user or the privacy risks involved. For example, a user agent can choose to show a loud / disruptive modal mediated dialog when it has enough confidence of the user’s intent or show a quiet / conservative UI hint when it doesn’t.

A user agent could also choose to control disruption of the user’s experience based on the risks involved. For example, when a directed identifier is being exchanged it can be more confident of the unintended consequeces and offer a more aggressive user experience, whereas when global identifiers are exchanged a more conservative user experience.

6.3.4. Cross-Site Correlation

This attack happens when multiple RPs collude to use their user’s data to correlate them and build a richer profile. When a user willingly provides their full name, email address, phone number, etc, to multiple relying parties, those relying parties can collaborate to build a profile of that user and their activity across collaborating sites. Sometimes this is referred to as joining since it amounts to a join of user records between the account databases of multiple RPs. This correlation and profile-building is outside the user’s control and entirely out of the user agent’s or IDP’s view.

  1. User signs into RP1 (which sells jewelry) with an IDP, providing to RP1 their email address user@email.example

  2. User signs into RP2 (which sells houses) with an IDP, providing to RP2 their email address user@email.example

  3. User browses the collection of wedding rings in RP1.

  4. Out of band, RP1 tells RP2 that user@email.example is shopping for wedding rings

  5. User browses the housing inventory in RP2.

  6. RP2 uses the fact that the user is shopping for wedding rings in RP1 to advertise and filters their housing inventory.

  7. User is surprised that RP2 knows that they are shopping for wedding rings.

The problem of RPs joining user data via back-channels is inherent to the proliferation of identifying user data. This can be solved by issuing directed identifiers that provide an effective handle to a user’s identity with a given IDP that is unique and therefore cannot be correlated with other RPs. In the past, there have been schemes to accomplish this using one-way hashes of, for example, the user’s name, the IDP, and the RP. These identifiers would be unique, and hence it would not be possible to correlate these with other RPs.