Created
September 29, 2022 17:46
-
-
Save samuelgoto/f1af7d5f2de0f24cbd18daa97b499b36 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<pre class='metadata'> | |
Title: Federated Credential Management API | |
Shortname: FedCM | |
Level: 1 | |
Status: w3c/CG-DRAFT | |
Group: fedidcg | |
ED: https://fedidcg.github.io/FedCM/ | |
Repository: fedidcg/FedCM | |
Editor: Sam Goto, Google Inc. https://google.com, [email protected] | |
Markup Shorthands: markdown yes, biblio yes | |
Default Biblio Display: inline | |
Text Macro: FALSE <code>false</code> | |
Text Macro: TRUE <code>true</code> | |
Text Macro: RP Relying Party | |
Text Macro: IDP Identity Provider | |
Abstract: A Web Platform API that allows users to login to websites with their federated accounts in a privacy preserving manner. | |
Test Suite: https://github.com/web-platform-tests/wpt/blob/master/credential-management/ | |
</pre> | |
<pre class=anchors> | |
spec: ecma262; urlPrefix: https://tc39.github.io/ecma262/ | |
type: dfn | |
text: time values; url: sec-time-values-and-time-range | |
text: promise; url: sec-promise-objects | |
text: internal method; url: sec-ordinary-object-internal-methods-and-internal-slots | |
spec: credential-management-1; urlPrefix: https://w3c.github.io/webappsec-credential-management/ | |
type: dictionary | |
text: CredentialRequestOptions; url: dictdef-credentialrequestoptions | |
type: enum | |
text: CredentialMediationRequirement; url: enumdef-credentialmediationrequirement | |
for: Credential | |
type: method | |
text: [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) | |
text: [[Create]](origin, options, sameOriginWithAncestors) | |
text: [[DiscoverFromExternalSource]]; url: algorithm-discover-creds | |
text: [[Store]](credential, sameOriginWithAncestors) | |
type: dfn | |
text: signal | |
text: same-origin with its ancestors; url: same-origin-with-its-ancestors | |
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/ | |
type: dfn | |
text: origin; for: html-origin-def; url: origin.html#concept-origin | |
text: Content-Type Metadata; url: urls-and-fetching.html#content-type | |
type: method | |
text: setTimeout; url: timers-and-user-prompts.html#dom-settimeout | |
spec: webidl; urlPrefix: https://webidl.spec.whatwg.org/ | |
for: Promise | |
type: dfn | |
text: reject | |
text: resolve | |
</pre> | |
<pre class=link-defaults> | |
spec:infra; type:dfn; text:list | |
spec:infra; type:dfn; text:user agent | |
spec:html; type:dfn; for:environment settings object; text:global object | |
spec:html; type:dfn; for:html-origin-def; text:origin | |
spec:webidl; type:dfn; text:resolve | |
</pre> | |
<style> | |
dl.domintro dt { | |
font-family: Menlo, Consolas, "DejaVu Sans Mono", Monaco, monospace; | |
padding-top: 0.5em; | |
padding-bottom: 1em; | |
} | |
dl.domintro dt a { | |
color: inherit; border-bottom-style: none; | |
} | |
dl.domintro dt code { | |
font-size: inherit; | |
} | |
.idp-normative-text { | |
background-color: rgba(165, 42, 42, 0.3); | |
margin: 16px 0px; | |
padding: 8px; | |
border-left: 8px solid brown; | |
} | |
</style> | |
<script src="https://fedidcg.github.io/FedCM/static/underscore-min.js"></script> | |
<script src="https://fedidcg.github.io/FedCM/static/raphael.min.js"></script> | |
<script src="https://fedidcg.github.io/FedCM/static/webfont.js"></script> | |
<script src="https://fedidcg.github.io/FedCM/static/typogram.js"></script> | |
<!-- ============================================================ --> | |
# Introduction # {#introduction} | |
<!-- ============================================================ --> | |
*This section is non-normative.* | |
As the web has evolved there have been ongoing privacy-oriented changes | |
(e.g [Safari](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/), | |
[Firefox](https://blog.mozilla.org/blog/2019/09/03/todays-firefox-blocks-third-party-tracking-cookies-and-cryptomining-by-default/), | |
[Chrome](https://blog.google/products/chrome/privacy-sustainability-and-the-importance-of-and/)) | |
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 | |
[=RPs=] and [=IDPs=]. This mediation requires user consent before permitting | |
the [=RPs=] and [=IDPs=] to know about their connection to the user. | |
<script type="text/typogram"> | |
.---------------------------------. .---------------------------------. | |
| .-----------------------------. | | .-----------------------------. | | |
| | "https://rp.example" | | | | "https://rp.example" | | | |
| '-----------------------------' | | '-----------------------------' | | |
| .-----------------------------. | | .-----------------------------. | | |
| | | | | | | | | |
| | Welcome to my website! | | | | Welcome to my website! | | | |
| | | | | | | | | |
| | | | | | | | | |
| +-----------------------------+ | | +-----------------------------+ | | |
| | Choose an account | | | | Sign-in to rp.example | | | |
| | to sign-in to rp.example | | | | with idp.example? | | | |
| | | | | | | | | |
| | .----. | | | | .----. | | | |
| | | :) | John Doe | | --> | | | :) | John Doe | | | |
| | '----' [email protected] | | | | '----' [email protected] | | | |
| | .----. | | | | | | | |
| | | :] | John Doe | | | | +-------------------------+ | | | |
| | '----' [email protected] | | | | | Continue as John | | | | |
| | | | | | +-------------------------+ | | | |
| '-----------------------------' | | '-----------------------------' | | |
'---------------------------------' '---------------------------------' | |
</script> | |
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, | |
fetch tokens, revoke the provided tokens, and allow for front-channel logout. | |
<!-- ============================================================ --> | |
## Use Cases ## {#use-cases} | |
<!-- ============================================================ --> | |
The below use case scenarios illustrate some basic supported flows. Each | |
supported flow below occurs inside an iframe or in an XHR request. Additional | |
scenarios, including sample code, are given in the | |
[[Identity-Use-Cases-in-Browser-Catalog]]. | |
<!-- ============================================================ --> | |
### Sign-up ### {#use-cases-sign-up} | |
<!-- ============================================================ --> | |
A Sign-up occurs when the user is registering a new account at the | |
[=Relying Party=] using their [=Identity Provider=]. | |
For instance, a user navigates to a [=Relying Party=] in their browser | |
and creates an account. The [=Relying Party=] displays supported | |
[=Identity Providers=] to the user who selects their favorite. The user | |
is prompted "Do you want to create an account with the [=Relying Party=]?". | |
Upon user agreement an [=account=] is created with the [=Relying Party=] and | |
the user has a [=session=] initialized. | |
<!-- ============================================================ --> | |
### Sign-in ### {#use-cases-sign-in} | |
<!-- ============================================================ --> | |
After a user navigates to a [=Relying Party=] in a browser and decides to | |
create an account by going through their [[#use-cases-sign-up]] flow, | |
there are two ways a user logs into their [=account=] once their [=session=] expires: | |
<!-- ============================================================ --> | |
#### Auto Sign-in #### {#use-cases-auto-sign-in} | |
<!-- ============================================================ --> | |
Auto Sign-in occurs when the [=Identity Provider=] has already gathered | |
permissions from the user to share their identity with the [=Relying Party=] | |
and automatically signs the user in. | |
For example, the user has previously executed the [[#use-cases-sign-up]] flow | |
and then changes from their phone to their laptop. On the new device the | |
user goes to the [=Relying Party=] and selects to sign-in using their | |
[=Identity Provider=]. The [=Identity Provider=] knows, and proves, the user | |
has signed up to the [=Relying Party=] and the [=Relying Party=] creates a | |
new [=session=] for the users [=account=]. | |
<!-- ============================================================ --> | |
#### Explicit Sign-in #### {#use-cases-explicit-sign-in} | |
<!-- ============================================================ --> | |
An explicit sign-in occurs when the [=Identity Provider=] believes it is | |
necessary to gather an explicit permission from the user to sign into a | |
[=Relying Party=], typically after the user goes through a | |
[[#use-cases-sign-out]] flow. | |
For example, after the user has done the [[#use-cases-sign-out]] flow of the | |
[=Relying Party=] they decide to log in again. The user visits the | |
[=Relying Party=] and selects their [=Identity Provider=] to sign-in. The | |
[=Identity Provider=] knows: | |
* the user already has an account with the [=Relying Party=]. | |
* the user has logged out of the [=Relying Party=]. | |
The user is then prompted, "Do you want to sign-in with the [=Relying Party=]?" | |
and upon user agreement the [=Relying Party=] creates a new [=session=] with | |
the users existing [=account=]. | |
<!-- ============================================================ --> | |
### Sign-out ### {#use-cases-sign-out} | |
<!-- ============================================================ --> | |
After a user navigates to a [=Relying Party=] in a browser and decides to | |
create an [=account=] by going through their [[#use-cases-sign-up]] flow, | |
there are two ways a user can clear their [=session=]s: | |
<!-- ============================================================ --> | |
#### RP Sign-out #### {#use-cases-rp-sign-out} | |
<!-- ============================================================ --> | |
The user can log out through the [=Relying Party=] by using a provided | |
sign-out button or link provided by the [=Relying Party=]. This then | |
removes the users [=session=] and, when the user visits the [=Relying Party=] | |
again they will need to go through the [[#use-cases-explicit-sign-in]] flow | |
in order to establish a new session. | |
<!-- ============================================================ --> | |
#### IDP Sign-out #### {#use-cases-idp-sign-out} | |
<!-- ============================================================ --> | |
The user can log out through the [=Identity Provider=] by using a provided | |
sign-out system provided by the [=Identity Provider=]. After using the | |
sign-out system the [=Identity Provider=] will log the user out of all | |
[=Relying Parties=] the user has signed into along with logging the user | |
out of the [=Identity Provider=] itself. Upon returning to any associated | |
[=Relying Party=], or the [=Identity Provider=], the user will have to | |
go through the [[#use-cases-explicit-sign-in]] flow. | |
<!-- ============================================================ --> | |
### Revocation ### {#use-cases-revocation} | |
<!-- ============================================================ --> | |
After a user has created an account with a [=Relying Party=] there are two | |
ways a user can cancel their account with the [=Relying Party=]: | |
<!-- ============================================================ --> | |
#### RP Revocation #### {#use-cases-rp-revocation} | |
<!-- ============================================================ --> | |
The user can delete their account through the [=Relying Party=] by using | |
the provided cancel account system. The [=Relying Party=] informs the | |
[=Identity Provider=] that the user has deleted (revoked) their account. | |
When the user returns to the [=Relying Party=] they will need to complete | |
the [[#use-cases-sign-up]] flow in order to access the site. | |
<!-- ============================================================ --> | |
#### IDP Revocation #### {#use-cases-idp-revocation} | |
<!-- ============================================================ --> | |
The user can delete their account with a [=Relying Party=] by revoking | |
[=Relying Party=] access through the [=Identity Provider=]. This can be done | |
by going to the [=Identity Provider=] and using their revoke access system. | |
Once access is revoked, when the user returns to the [=Relying Party=] they | |
will need to complete the [[#use-cases-sign-up]] flow in order to access the | |
site. | |
<!-- ============================================================ --> | |
### Access ### {#use-cases-access} | |
<!-- ============================================================ --> | |
The [=Identity Provider=] while authenticating the user may also authorize | |
access to users resources such as calendars, contacts, etc. The granting | |
of access can be done at either sign-up or post sign-up by requesting | |
permission from the user. | |
For example, a user executes the [[#use-cases-sign-up]] flow with a | |
[=Relying Party=]. During the flow the [=Relying Party=] has informed the | |
[=Identity Provider=] they need calendar access for the user. The user will | |
be presented with a prompt, "Do you want to give access to your Calendar | |
to the [=Relying Party=]?". The user consents to providing access and when | |
the flow is complete the [=Relying Party=] shows the user their calendar | |
entries provided by the [=Identity Provider=]. | |
<!-- ============================================================ --> | |
# Examples # {#examples} | |
<!-- ============================================================ --> | |
This specification defines a new {{IdentityCredential}} type and internal | |
algorithms to allow the exchange of identity between [=IDP=]s and [=RP=]s. | |
When it succeeds, it returns to the [=RP=] a signed [=token=] which the | |
[=RP=] can use to authenticate the user. | |
<div class=example> | |
Example showing how a website allowing for a single logged in account | |
could be implemented. | |
```html | |
<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 `token` can be | |
// extracted. | |
return await navigator.credentials.get({ | |
mediation: "optional", // "optional" is the default | |
identity: { | |
providers: [{ | |
configURL: "https://idp.example/manifest.json", | |
clientId: "123", | |
nonce: nonce | |
}] | |
} | |
}); | |
} | |
</script> | |
</body> | |
</html> | |
``` | |
</div> | |
<!-- ============================================================ --> | |
# Terminology # {#terminology} | |
<!-- ============================================================ --> | |
[[HTML]] defines an [=origin=] as the tuple of a scheme, hostname, and port that | |
provides the main security boundary on the web. | |
: <dfn>account</dfn> | |
:: TODO(goto): find existing definition. | |
: <dfn>authentication</dfn> | |
:: Process used by an [=Identity Provider=] to achieve sufficient confidence in | |
the binding between the user and a presented identity. | |
Note that in some discussions and documentation, the term _authentication_ is | |
used to refer to the [=federated sign-in=] process. However, the user does not | |
authenticate to the [=RP=] during [=federated sign-in=]. The user | |
authenticates to the [=IDP=], which then provides a claim to the [=RP=] | |
asserting the user’s identity. The user does not prove their identity to the | |
[=RP=]. | |
See also: | |
* [[OIDC-Connect-Core#Terminology]] | |
* [[OIDC-Connect-Core#Authentication]] | |
* [[SAML-Glossary]] | |
: <dfn>client id</dfn> | |
:: Each [=IDP=] assigns to each [=RP=] a [=client id=] to uniquely identify the [=RP=]. Note that | |
this ID is dependent on both the [=IDP=] and the [=RP=], but the [=client id=] of an [=RP=] only | |
needs to be unique with respect to any other [=client id=] within the same [=IDP=]. | |
: <dfn>directed identifier</dfn> | |
:: A [=user identifier=] that that is unique for each [=site=] the user visits. A | |
goal of anti-tracking policy is to promote [=user identifiers=] to become | |
[=directed identifiers=]. | |
: <dfn>global identifier</dfn> | |
:: A string that identifies a particular [=user=] independent of which site | |
they're visiting (e.g. email addresses and phone numbers). Users generally | |
have relatively few global identifiers and can usually list and recognize | |
them. A goal of anti-tracking policy is to prevent [=user identifiers=] from | |
becoming [=global identifiers=]. | |
: <dfn>high-level API</dfn> | |
:: A use case specific API, as opposed to a [=low-level API=]. See also | |
[high level vs low level](https://w3ctag.github.io/design-principles/#high-level-low-level). | |
: <dfn>token</dfn> | |
:: TODO(goto): find existing definition. | |
: <dfn>Identity Provider</dfn> | |
: <dfn>IDP</dfn> | |
:: A service that has information about the user and can grant that information | |
to [=Relying Parties=]. | |
See also: | |
* [[OIDC-Connect-Core#Terminology]] | |
: <dfn>Tracker</dfn> | |
:: A third-party origin that has injected a script within the [=RP=] and that is not an [=IDP=]. Its | |
goal is to track the user's behavior and build profiles that it can then sell to the highest | |
bidder. | |
: <dfn>joining</dfn> | |
:: TODO(goto): find existing definition. | |
: <dfn>low-level API</dfn> | |
:: A general purpose API, as opposed to a [=high-level API=]. See also | |
[high level vs low level](https://w3ctag.github.io/design-principles/#high-level-low-level) | |
: <dfn>minting</dfn> | |
:: The act of a new token being creating | |
: <dfn>out-of-band</dfn> | |
:: Outside of the user agent's context. | |
: <dfn>Relying Party</dfn> | |
: <dfn>RP</dfn> | |
: <dfn noexport>Website</dfn> | |
:: A service that requests user information from an [=Identity Provider=] for | |
[=federated sign-in=] or for other purposes. | |
See also: | |
* [[OIDC-Connect-Core#Terminology]] | |
* [[SAML-Glossary]] | |
: <dfn>session</dfn> | |
:: TODO(goto): find existing definition. | |
: <dfn>Federated sign-in</dfn> | |
:: Process used by a [=Relying Party=] to obtain a [=user identifier=] from an | |
[=Identity Provider=] to which the user performed [=authentication=]. | |
See also: | |
* [[OIDC-Connect-Core]] | |
: <dfn>site</dfn> | |
:: A set of [=origins=] that are all [=site/same site=] with each other. Note that | |
there are problems ([[PSL-PROBLEMS]]) with using [=registrable domains=] as | |
a logical boundary. | |
: <df>unsanctioned tracking</dfn> | |
:: [[UNSANCTIONED-TRACKING]] | |
: <dfn>user</dfn> | |
:: A human or program that controls a user agent. | |
: <dfn>user identifier</dfn> | |
:: A pair of a [=site=] and a (potentially-large) integer | |
allocated by that site that is used to identify a [=user=] on that site. A | |
single user will generally have many user IDs that refer to them, and a single | |
site may or may not know that multiple user identifiers refer to the same user. | |
: <dfn>Privacy Policy</dfn> | |
:: The policies described at {{client_metadata/privacy_policy_url}}. | |
: <dfn>Terms of Service</dfn> | |
:: The policies described at {{client_metadata/terms_of_service_url}}. | |
: <dfn>registered</dfn> | |
:: The account is `registered` if the user agent thinks that the user has registered an account in | |
the [=RP=] using the account from the [=IDP=]. | |
: <dfn>unregistered</dfn> | |
:: The account is `unregistered` if it's not registered. | |
<!-- ============================================================ --> | |
# High Level Design # {#high-level-design} | |
<!-- ============================================================ --> | |
At a high level, the Identity Federation Management API works by the | |
intermediation of cooperating [=IDP=]s and [=RP=]s. | |
The [[#idp-api]] and the [[#rp-api]] defines a set of HTTP APIs that cooperating | |
[=IDP=]s and [=IDP=]s exposes as well as the entry points in the [[#browser-api]] | |
that they can use. | |
<script type="text/typogram"> | |
+-----------+ +-----------+ +-----------+ | |
| | | | | | | |
| Relying | | User | | Identity | | |
| Party | | Agent | | Provider | | |
| | | | | | | |
| | | | | | | |
| | +----------->*-+ Credential| +------------->*-+ manifest | | |
| | | | Management| | | | | |
| | | | API | +------------->*-+ accounts | | |
| | | | | | | | | |
| | | | | +------------->*-+ client | | |
| | | | | | | metadata | | |
| | | | | | | | | |
| | | | | +------------->*-+ token | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| +-------+ | | | +-------+ | | | +-------+ | | |
| | JS +-+---+ +--------+-+ HTTP +-+-----+ +------+-+-+ JS | | | |
| +-------+ | | | +-------+ | | | +-------+ | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| logout +-*<------+ | logoutRPs +-*<----------+ | | | |
| | | | | | | |
| | | | | | | |
+-----------+ +-----------+ +-----------+ | |
</script> | |
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. | |
<script type="text/typogram"> | |
Relying User Identity | |
Party Agent Provider | |
| | | | |
| "navigator.credentials.get()" | | | |
|------------------------------>| | | |
| | | | |
| | "GET /.well-known/web-identity" | | |
| |-------------------------------->| | |
| |<--------------------------------| "{" | |
| | | "provider_urls: [ ... ]" | |
| | | "}" | |
| | | | |
| | "GET /config.json" | | |
| |-------------------------------->| | |
| |<--------------------------------| "{" | |
| | | "id_token_endpoint: ...," | |
| | | "accounts_endpoint: ...," | |
| | | "client_metadata_endpoint: ...," | |
| | | "branding: ...," | |
| | | "}" | |
| | | | |
| | "GET client_metadata.php" | | |
| | "client_id" | | |
| |-------------------------------->| | |
| |<--------------------------------| "{" | |
| | | "terms_of_service_url: ...," | |
| | | "privacy_policy_url: ...," | |
| | | "}" | |
| | | | |
| | | | |
| | "GET accounts.php" | | |
| | "cookies" | | |
| |-------------------------------->| | |
| |<--------------------------------| "{" | |
| | | "accounts: [ ... ]" | |
| | | "}" | |
| | | | |
| | | | |
| +---------+ | | |
| | | | | |
| | | | | |
| | | "account chooser" | | |
| | | | | |
| | | | | |
| +-------->| | | |
| | | | |
| | | | |
| | | | |
| | "POST token.php" | | |
| | "client_id, cookies, account" | | |
| |-------------------------------->| | |
| |<--------------------------------| "{" | |
| | | "token: ...," | |
| | | "}" | |
| "{ token }" | | | |
|<------------------------------| | | |
| | | | |
| | | | |
-+- -+- -+- | |
</script> | |
This document defines the APIs in the following order: | |
1. The [[#idp-api]] | |
1. The [[#rp-api]] | |
1. The [[#browser-api]] | |
<!-- ============================================================ --> | |
# The Identity Provider API # {#idp-api} | |
<!-- ============================================================ --> | |
The [=IDP=] proactively and cooperatively exposes itself as a comformant agent | |
by exposing a series of HTTP endpoints: | |
1. A [[#idp-api-manifest]] endpoint in an agreed upon location that points to | |
1. An [[#idp-api-accounts-endpoint]] endpoint | |
1. A [[#idp-api-client-id-metadata-endpoint]] endpoint | |
1. An [[#idp-api-id-token-endpoint]] endpoint | |
Issue: The algorithms used for these endpoints need to [integrate](https://github.com/fedidcg/FedCM/issues/261) with [=fetch=]. | |
<!-- ============================================================ --> | |
## Manifest ## {#idp-api-manifest} | |
<!-- ============================================================ --> | |
The manifest discovery endpoint is an endpoint | |
which serves as a discovery device to other endpoints provided by the | |
[=IDP=]. | |
The manifest discovery endpoint is fetched: | |
(a) **without** cookies, | |
(b) **with** a special [[#Sec-FedCM-CSRF]] header, | |
(c) **without** a [[RFC9110#header.referer|Referer]] header, and | |
(c) **without** following [[RFC9110#header.location|HTTP redirects]]. | |
For example: | |
<div class=example> | |
```http | |
GET /config.json HTTP/1.1 | |
Host: idp.example | |
Accept: application/json | |
Sec-FedCM-CSRF: ?1 | |
``` | |
</div> | |
The file is parsed expecting [=Manifest=] JSON object. | |
The <dfn>Manifest</dfn> JSON object has the following properties: | |
<dl dfn-type="argument" dfn-for="Manifest"> | |
: <dfn>accounts_endpoint</dfn> (required) | |
:: A URL that points to an HTTP API that complies with the [[#idp-api-accounts-endpoint]] API. | |
: <dfn>client_metadata_endpoint</dfn> (required) | |
:: A URL that points to an HTTP API that complies with the [[#idp-api-client-id-metadata-endpoint]] API. | |
: <dfn>id_token_endpoint</dfn> (required) | |
:: A URL that points to an HTTP API that complies with the [[#idp-api-id-token-endpoint]] API. | |
Issue: the id_token_endpoint may be confused with OIDC's endpoint, so we are looking for alternatives [here](https://github.com/fedidcg/FedCM/issues/254). | |
: <dfn>branding</dfn> (optional) | |
:: A set of [=Branding JSON=] options. | |
</dl> | |
The <dfn>Branding JSON</dfn> enables an [=IDP=] to express their branding | |
preferences, which may be used by [=user agents=] to customize the consent 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. | |
It may have the following properties: | |
<dl dfn-type="argument" dfn-for="manifest_branding"> | |
: <dfn>background_color</dfn> (optional) | |
:: Background [=color=] for [=IDP=]-branded widgets such as buttons. | |
: <dfn>color</dfn> (optional) | |
:: [=color=] for text on [=IDP=] branded widgets. | |
: <dfn>icons</dfn> (optional) | |
:: A list of [=Icon JSON=] objects. | |
</dl> | |
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 <dfn>Icon JSON</dfn> may have the following properties: | |
<dl dfn-type="argument" dfn-for="manifest_branding_icons"> | |
: <dfn>url</dfn> (required) | |
:: 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](https://www.w3.org/TR/appmanifest/#icon-masks) specification. | |
: <dfn>size</dfn> (optional) | |
:: The width/height of the square icon. The size may be omitted if the icon is in a vector | |
graphic format (like SVG). | |
</dl> | |
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 <dfn>color</dfn> is a subset of CSS <<color>> syntax, namely <<hex-color>>s, ''hsl()''s, ''rgb()''s and <<named-color>>. | |
For example: | |
<div class=example> | |
```json | |
{ | |
"accounts_endpoint": "/accounts.php", | |
"client_metadata_endpoint": "/metadata.php", | |
"id_token_endpoint": "/idtokens.php", | |
"branding": { | |
"background_color": "green", | |
"color": "0xFFEEAA", | |
"icons": [{ | |
"url": "https://idp.example/icon.ico", | |
"size": 10 | |
}] | |
} | |
} | |
``` | |
</div> | |
<!-- ============================================================ --> | |
## Accounts List ## {#idp-api-accounts-endpoint} | |
<!-- ============================================================ --> | |
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** a special [[#Sec-FedCM-CSRF]] header, | |
(c) **without** a [[RFC9110#header.referer|Referer]] header, and | |
(d) **without** following [[RFC9110#header.location|HTTP redirects]]. | |
For example: | |
<div class=example> | |
```http | |
GET /accounts_list.php HTTP/1.1 | |
Host: idp.example | |
Accept: application/json | |
Cookie: 0x23223 | |
Sec-FedCM-CSRF: ?1 | |
``` | |
</div> | |
The response is expected to have the following properties: | |
<dl dfn-type="argument" dfn-for="accounts_endpoint_response"> | |
: <dfn>accounts</dfn> (required) | |
:: A list of [=Account JSON=]. | |
</dl> | |
Every <dfn>Account JSON</dfn> is expected to have the following properties: | |
<dl dfn-type="argument" dfn-for="account_json"> | |
: <dfn>id</dfn> (required) | |
:: The account unique identifier. | |
: <dfn>name</dfn> (required) | |
:: The user's full name. | |
: <dfn>email</dfn> (required) | |
:: The user's email address. | |
: <dfn>given_name</dfn> (optional) | |
:: The user's given name. | |
: <dfn>approved_clients</dfn> (optional) | |
:: A list of [=RP=]s (in the form of [=Client ID=]s) this account is already registered with. | |
Used in the [=request consent to sign-up=] to allow the [=IDP=] to control whether to show | |
the [=Privacy Policy=] and the [=Terms of Service=]. | |
</dl> | |
For example: | |
<div class=example> | |
```json | |
{ | |
"accounts": [{ | |
"id": "1234", | |
"given_name": "John", | |
"name": "John Doe", | |
"email": "[email protected]", | |
"picture": "https://idp.example/profile/123", | |
"approved_clients": ["123", "456", "789"] | |
}, { | |
"id": "5678", | |
"given_name": "Johnny", | |
"name": "Johnny", | |
"email": "[email protected]", | |
"picture": "https://idp.example/profile/456" | |
"approved_clients": ["abc", "def", "ghi"] | |
}] | |
} | |
``` | |
</div> | |
Issue: [Clarify](https://github.com/fedidcg/FedCM/issues/218) the IDP API response when the user is not signed in. | |
<!-- ============================================================ --> | |
## Client Metadata ## {#idp-api-client-id-metadata-endpoint} | |
<!-- ============================================================ --> | |
The client metadata endpoint provides metadata about [=RP=]s. | |
The client medata endpoint is fetched | |
(a) **without** cookies, | |
(b) **with** a special [[#Sec-FedCM-CSRF]] header, | |
(c) **with** a [[RFC9110#header.referer|Referer]] header indicating the [=RP=]'s origin | |
(as if [[referrer-policy#referrer-policy-strict-origin|Referer-Policy: strict-origin]] | |
was in use), and | |
(d) **without** following [[RFC9110#header.location|HTTP redirects]]. | |
The user agent also passes the **client_id**. | |
For example: | |
<div class=example> | |
```http | |
GET /client_medata.php?client_id=1234 HTTP/1.1 | |
Host: idp.example | |
Referer: https://rp.example/ | |
Accept: application/json | |
Sec-FedCM-CSRF: ?1 | |
``` | |
</div> | |
The file is parsed expecting the following properties: | |
<dl dfn-type="argument" dfn-for="client_metadata"> | |
: <dfn>privacy_policy_url</dfn> (optional) | |
:: A link to the [=RP=]'s [=privacy policy=]. | |
: <dfn>terms_of_service_url</dfn> (optional) | |
:: A link to the [=RP=]'s [=terms of service=]. | |
</dl> | |
For example: | |
<div class=example> | |
```json | |
{ | |
"privacy_policy_url": "https://rp.example/clientmetadata/privacy_policy.html", | |
"terms_of_service_url": "https://rp.example/clientmetadata/terms_of_service.html" | |
} | |
``` | |
</div> | |
<!-- ============================================================ --> | |
## ID Token ## {#idp-api-id-token-endpoint} | |
<!-- ============================================================ --> | |
The ID Token endpoint is responsible for [=minting=] a new [=token=] for the user. | |
The ID Token endpoint is fetched | |
(a) as a **POST** request, | |
(b) **with** [=IDP=] cookies, | |
(c) **with** a [[RFC9110#header.referer|Referer]] header indicating the [=RP=]'s origin | |
(as if [[referrer-policy#referrer-policy-strict-origin|Referer-Policy: strict-origin]] | |
was in use), | |
(d) **with** a special [[#Sec-FedCM-CSRF]] header, and | |
(e) **without** following [[RFC9110#header.location|HTTP redirects]]. | |
It will also contain the following parameters in the request body `application/x-www-form-urlencoded`: | |
<dl dfn-type="argument" dfn-for="id_token_endpoint_request"> | |
: <dfn>client_id</dfn> | |
:: The [=RP=]'s [=client id=]. | |
: <dfn>nonce</dfn> | |
:: The request nonce | |
: <dfn>account_id</dfn> | |
:: The account identifier that was selected. | |
: <dfn>disclosure_text_shown</dfn> | |
:: 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 consent to sign-up=] algorithm for new users but | |
not by the [=sign-in=] algorithm for returning users. | |
</dl> | |
For example: | |
<div class=example> | |
```http | |
POST /fedcm_token_endpoint HTTP/1.1 | |
Host: idp.example | |
Referer: https://rp.example/ | |
Content-Type: application/x-www-form-urlencoded | |
Cookie: 0x23223 | |
Sec-FedCM-CSRF: ?1 | |
account_id=123&client_id=client1234&nonce=Ct60bD&disclosure_text_shown=true | |
``` | |
</div> | |
<div class=idp-normative-text> | |
An [=IDP=] MUST check the referrer 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 referrer is | |
represented by the [=client id=]. As the [=client ids=] are [=IDP=]-specific, the [=user agent=] | |
cannot perform this check. | |
</div> | |
The response is parsed as a JSON file expecting the following properties: | |
<dl dfn-type="argument" dfn-for="id_token_endpoint_response"> | |
: <dfn>token</dfn> | |
:: The resulting [=token=]. | |
</dl> | |
The content of the [=token=] is opaque to the user agent and can contain | |
anything that the [=Identity Provider=] would like to pass to the | |
[=Relying Party=] to facilitate the login. | |
NOTE: For [=Identity Providers=], it is worth considering how | |
[portable](https://github.com/fedidcg/FedCM/issues/314) accounts are. | |
Portability is left entirely up to [=Identity Providers=], who can choose | |
between a variety of different mechanisms to accomplish it | |
(e.g. [OIDC's Account Porting](https://openid.net/specs/openid-connect-account-porting-1_0.html)). | |
For example: | |
<div class=example> | |
```json | |
{ | |
"token" : "eyJC...J9.eyJzdWTE2...MjM5MDIyfQ.SflV_adQssw....5c" | |
} | |
``` | |
</div> | |
<!-- ============================================================ --> | |
## <code><dfn data-export="">`Sec-FedCM-CSRF`</dfn></code> ## {#Sec-FedCM-CSRF} | |
<!-- ============================================================ --> | |
All FedCM HTTP requests sent by the browser must contain a header | |
`Sec-FedCM-CSRF` with value `?1`. This allows servers to verify that the request | |
was initiated by the browser and not untrusted JavaScript because | |
it is a [=forbidden header name=]. | |
<!-- ============================================================ --> | |
# The Relying Party API # {#rp-api} | |
<!-- ============================================================ --> | |
[=RPs=] expose a [[#rp-api-logout-endpoint]] to facilitate with [[#use-cases-idp-sign-out]]. | |
<!-- ============================================================ --> | |
## Logout ## {#rp-api-logout-endpoint} | |
<!-- ============================================================ --> | |
When [=IDP=]s call the [[#browser-api-idp-sign-out]] API, every [=RP=] gets a | |
chance to log the user out (e.g. clear cookies, clear local storage) | |
via the logout endpoint. | |
The logout endpoint is an endpoint that is registered with the [=IDP=] | |
[=out-of-band=]. | |
The logout endpoint is called | |
(a) with a **GET** and | |
(b) with the [=RP=]'s cookies. | |
Note: the logout API introduces a credentialed request from the [=IDP=] to | |
the [=RP=]s, so it exposes a potential tracking surface area. It is a fairly | |
limited and controlled tracking area because the logout API is only available | |
when accounts **and** sessions are already established between the [=IDP=] and | |
the [=RP=]. | |
<!-- ============================================================ --> | |
# The Browser API # {#browser-api} | |
<!-- ============================================================ --> | |
The Browser API exposes APIs to [=RP=]s and [=IDP=]s to call and intermediates | |
the exchange of the user's identity. | |
The Sign-up and Sign-in APIs are used by the [=Relying Party=]s to ask the browser | |
to intermediate the relationship with the [=Identity Provider=] and the | |
provisioning of a [=token=]. | |
NOTE: The [=Relying Party=] 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=] in the form of a signed [[JWT]] which it can use to | |
authenticate the user. | |
<div class=example> | |
```js | |
const credential = await navigator.credentials.get({ | |
identity: { | |
providers: [{ | |
configURL: "https://idp.example/manifest.json", | |
clientId: "123", | |
}] | |
} | |
}); | |
``` | |
</div> | |
To do so, this specification does three things: | |
First, it introduces a new {{Credential}} type, called {{IdentityCredential}}. | |
Second, it introduces an extension to {{CredentialRequestOptions}}. | |
Lastly, it overrides {{IdentityCredential}}'s implementation of | |
{{Credential/[[DiscoverFromExternalSource]]}}. | |
<!-- ============================================================ --> | |
## The State Machine ## {#browser-api-state-machine} | |
<!-- ============================================================ --> | |
Each [=user agent=] keeps track of a global <dfn>state machine map</dfn>, an initially empty | |
[=map=]. The [=map/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 [=map/values=] in the [=state machine map=] are <dfn>AccountState</dfn> objects which have | |
the following properties: | |
<dl dfn-type="argument" dfn-for="AccountState"> | |
: <dfn>registration state</dfn> | |
:: Keeps track of whether the user agent is aware that the user has registered an account in | |
the [=RP=] or not. | |
Can be [=registered=] or [=unregistered=] (by default). | |
: <dfn>allows logout</dfn> | |
:: Boolean which keeps track of whether the user agent would allow the |account| to be logged | |
out via {{IdentityCredential/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 {{IdentityCredential/logoutRPs()}} has not yet been called | |
on this account after the last successful {{IdentityCredential}} creation with this account. | |
</dl> | |
TODO: add an ASCII image to explain how the states fit into the algorithms. | |
<!-- ============================================================ --> | |
## The IdentityCredential Interface ## {#browser-api-identity-credential-interface} | |
<!-- ============================================================ --> | |
This specification introduces a new type of {{Credential}}, called an {{IdentityCredential}}: | |
<pre class="idl"> | |
[Exposed=Window, SecureContext] | |
interface IdentityCredential : Credential { | |
readonly attribute USVString? token; | |
}; | |
</pre> | |
<dl> | |
: <b>{{IdentityCredential/token}}</b> | |
:: The {{IdentityCredential/token}}'s attribute getter returns the value it is set to. | |
It represents the minted [=token=] provided by the [=IDP=]. | |
</dl> | |
The main entrypoint in this specification is through the entrypoints exposed | |
by the [[CM]] API. | |
<!-- ============================================================ --> | |
## The CredentialRequestOptions ## {#browser-api-credential-request-options} | |
<!-- ============================================================ --> | |
This specification starts by introducing an extension to the | |
{{CredentialRequestOptions}} object: | |
<pre class="idl"> | |
partial dictionary CredentialRequestOptions { | |
IdentityCredentialRequestOptions identity; | |
}; | |
</pre> | |
The {{IdentityCredentialRequestOptions}} contains a list of | |
{{IdentityProvider}}s that the [=Relying Party=] supports and has | |
pre-registered with (i.e. it has a `clientId`). | |
<xmp class=idl> | |
dictionary IdentityCredentialRequestOptions { | |
sequence<IdentityProvider> providers; | |
}; | |
</xmp> | |
Each {{IdentityProvider}} represents an [=Identity Provider=] that | |
the [=Relying Party=] supports (e.g. that it has a pre-registration agreement with). | |
<xmp class=idl> | |
dictionary IdentityProvider { | |
required USVString configURL; | |
required USVString clientId; | |
USVString nonce; | |
}; | |
</xmp> | |
<dl> | |
: <b>{{IdentityProvider/configURL}}</b> | |
:: The URL of the configuration file for the identity provider. | |
: <b>{{IdentityProvider/clientId}}</b> | |
:: The [=client id=] provided to the [=RP=] out of band by the [=IDP=] | |
: <b>{{IdentityProvider/nonce}}</b> | |
:: 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. | |
</dl> | |
<!-- ============================================================ --> | |
## The <code>\[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)</code> internal method ## {#browser-api-rp-sign-in} | |
<!-- ============================================================ --> | |
The {{Credential/[[DiscoverFromExternalSource]]}} algorithm runs in parallel inside the | |
[[CM#algorithm-request]] to request credentials and returns a set of {{IdentityCredential}} for the | |
requested [=Identity Provider=]s. | |
This [=internal method=] accepts three arguments: | |
<dl dfn-type="argument" dfn-for="IdentityCredential/[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)"> | |
: <dfn>origin</dfn> | |
:: This argument is the [=relevant settings object=]'s | |
[=environment settings object/origin=], as determined by the | |
calling {{CredentialsContainer/get()}} implementation, i.e., | |
{{CredentialsContainer}}'s <a abstract-op>Request a `Credential`</a> | |
abstract operation. | |
: <dfn>options</dfn> | |
:: This argument is a {{CredentialRequestOptions}} object whose | |
{{CredentialRequestOptions/identity}} member contains an | |
{{IdentityCredentialRequestOptions}} object specifying the exchange options. | |
: <dfn>sameOriginWithAncestors</dfn> | |
:: 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-1]] level. | |
See [[#permissions-policy-integration]]. As such, |sameOriginWithAncestors| is unused. | |
</dl> | |
NOTE: The {{CredentialRequestOptions/mediation}} flag is currently not used. | |
The {{CredentialRequestOptions/signal}} is used as an abort signal for the | |
requests. | |
<div algorithm> | |
When the {{IdentityCredential}}'s | |
<dfn for="IdentityCredential" method>\[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)</dfn>\ | |
algorithm is invoked, the user agent MUST execute the following steps: | |
1. Assert: |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"] [=map/exists=]. | |
1. Assert: |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"] [=list/size=] is 1. | |
Issue: Support choosing accounts from multiple [=Identity Provider=]s, as described [here](https://github.com/fedidcg/FedCM/issues/319). | |
1. Run {{setTimeout}} passing a [=task=] which throws a {{NetworkError}}, after a timeout of | |
120 seconds. | |
Note: the purpose of having a timer here is to avoid leaking the reason causing this | |
method to return null. 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 providing consent to share the [=IDP=] account information with the [=RP=]. | |
1. Let |provider| be |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"][0]. | |
1. Let |credential| be the result of running the [=potentially create IdentityCredential=] | |
algorithm with |provider|. | |
1. If |credential| is null, wait for the task that throws a {{NetworkError}}, otherwise return | |
|credential|. | |
</div> | |
<div algorithm> | |
To <dfn>potentially create IdentityCredential</dfn>, given an {{IdentityProvider}} |provider|: | |
1. Let |manifest| be the result of running the [=fetch the manifest=] | |
algorithm with |provider|. | |
1. If |manifest| is null, return null. | |
1. Let |accountsList| be the result of running the | |
[=fetch the accounts list=] algorithm with |manifest| and |provider|. | |
1. If |accountsList|'s size is 0, return null. | |
1. If |accountsList|'s size is 1: | |
1. Let |account| be |accountsList|[0]. | |
1. Let |accountState| be the result of running the [=compute account state=] algorithm | |
given |provider| and |account|. | |
1. If |accountState|'s {{AccountState/registration state}} is [=unregistered=] then run the | |
[=request consent to sign-up=] algorithm with |account|, |accountState|, |manifest|, and | |
|provider|. | |
1. Otherwise, show a dialog to request user consent to sign in via |account|. | |
1. If the user consents, run the [=sign-in=] algorithm with |accountState|. | |
1. Otherwise: | |
1. Let |account| be the result of running the [=select an account=] from the | |
|accountsList|. | |
1. If |account| is null, return null. | |
1. Let |accountState| be the result of running the [=compute account state=] algorithm | |
given |provider| and |account|. | |
1. If |accountState|'s {{AccountState/registration state}} is [=unregistered=] then run the | |
[=request consent to sign-up=] algorithm with |account|, |accountState|, |manifest|, and | |
|provider|. | |
1. Otherwise, run the [=sign-in=] algorithm with |accountState|. | |
1. If |accountState|'s {{AccountState/registration state}} is [=unregistered=] then return null. | |
1. Let |token| be the result of running the [=create tokens=] algorithm with |accountState|, | |
|account|'s {{account_json/id}}, and |provider|. | |
1. Let |credential| be a new {{IdentityCredential}}. | |
1. Set |credential|'s {{IdentityCredential/token}} to |token|. | |
1. Return |credential|. | |
</div> | |
<div algorithm> | |
To <dfn>compute account state</dfn> given an {{IdentityProvider}} |provider| and an | |
[=Account JSON=] |account|, run the following steps: | |
1. Let |idpURL| be |provider|'s {{IdentityProvider/configURL}}. | |
1. Let |idpOrigin| be the [=origin=] corresponding to |idpURL|. | |
1. Let |rpOrigin| be [=this=]'s [=Document/origin=]. | |
1. Let |accountId| be |account|'s {{account_json/id}}. | |
1. Let |triple| be (|rpOrigin|, |idpOrigin|, |accountId|). | |
1. If [=state machine map=][|triple|] does not exist, set [=state machine map=][|triple|] to a | |
new [=AccountState=]. | |
1. Let |accountState| be [=state machine map=][|triple|]. | |
1. Return |accountState|. | |
</div> | |
<div algorithm> | |
To <dfn>fetch the accounts list</dfn> given a [=Manifest=] |manifest| and an {{IdentityProvider}} | |
|provider|: | |
1. Let the |accountsEndpoint| url be the relative url | |
|manifest|["{{Manifest/accounts_endpoint}}"] of |provider|'s {{IdentityProvider/configURL}}. | |
1. Let |accountsList| be the result of fetching the |accountsEndpoint| with the | |
[=Identity Provider=]'s cookies. This request MUST NOT follow | |
[[RFC9110#header.location|HTTP redirects]] and instead return null if there is any error. | |
See also [[#idp-api-accounts-endpoint]]. | |
Issue: The credentialed fetch in this algorithm can lead to a timing attack that leaks the user's identities before the user consents. This is an active area of investigation that is being explored [here](https://github.com/fedidcg/FedCM/issues/230#issuecomment-1089040953). | |
1. Return |accountsList|. | |
Issue: We should validate the accounts list returned here for repeated ids, as described [here](https://github.com/fedidcg/FedCM/issues/336). | |
</div> | |
<div algorithm> | |
To <dfn>request consent to sign-up</dfn> the user with a given [=Account JSON=] |account|, an | |
[=AccountState=] |accountState|, a [=Manifest=] |manifest|, and an {{IdentityProvider}} | |
|provider|: | |
1. Let |metadata| be the result of running the [=fetch the client metadata=] | |
algorithm with |manifest| and |provider|. | |
1. If |metadata| is not null, | |
|metadata|["{{client_metadata/privacy_policy_url}}"] is defined, and the |provider|'s | |
{{IdentityProvider/clientId}} is not in the list of | |
|account|["{{account_json/approved_clients}}"], then display the | |
|metadata|["{{client_metadata/privacy_policy_url}}"] link. | |
1. If |metadata| is not null, |metadata|["{{client_metadata/terms_of_service_url}}"] is defined, | |
and the |provider|'s {{IdentityProvider/clientId}} is not in the list of | |
|account|["{{account_json/approved_clients}}"], then display the | |
|metadata|["{{client_metadata/terms_of_service_url}}"] link. | |
1. Prompt the user to gather explicit intent to create an account. The user agent MAY use the | |
[=Branding JSON=] to inform the style choices of its UI. | |
1. If the user does not provide consent, return. | |
1. Change |accountState|'s {{AccountState/registration state}} from [=unregistered=] to | |
[=registered=]. | |
1. Change |accountState|'s {{AccountState/allows logout}} from false to true. | |
</div> | |
<div algorithm> | |
To <dfn noexport>fetch the client metadata</dfn> given a [=Manifest=] |manifest| and an | |
{{IdentityProvider}} |provider|, run the following steps: | |
1. Let the |clientMetadataEndpoint| url be the relative url | |
|manifest|["{{Manifest/client_metadata_endpoint}}"] of |provider|'s | |
{{IdentityProvider/configURL}}. | |
1. Let the |clientMetadata| be the result of fetching the |clientMetadataEndpoint| with the | |
[[#Sec-FedCM-CSRF|Sec-FedCM-CSRF]] header but without the [=Identity Provider=]'s cookies. This request MUST NOT follow [[RFC9110#header.location|HTTP redirects]] and instead return | |
null if there is any error. See also [[#idp-api-client-id-metadata-endpoint]]. | |
1. Return |clientMetadata|. | |
</div> | |
<div algorithm> | |
To <dfn>select an account</dfn> given an |accountsList|: | |
1. Assert |accountsList|'s [=list/size=] is greater than 1. | |
1. Display an account chooser displaying the options from |accountsList|. | |
1. Let |account| be the {{account_json/id}} of the account that the user | |
manually selects from the accounts chooser, or null if no account is selected. | |
1. Return |account| | |
</div> | |
<div algorithm> | |
To <dfn>sign-in</dfn> the user with a given an [=AccountState=] |accountState|: | |
1. Assert that |accountState|'s {{AccountState/registration state}} is [=registered=] | |
1. Change |accountState|'s {{AccountState/allows logout}} from false to true. | |
</div> | |
<div algorithm> | |
To <dfn>create tokens</dfn> given an [=AccountState=] |accountState|, a {{USVString}} |accountId|, | |
and an {{IdentityProvider}} |provider|: | |
1. Assert |accountState|'s {{AccountState/registration state}} is [=registered=]. | |
1. Assert |accountState|'s {{AccountState/allows logout}} is true. | |
1. Let |request| be a new object. | |
1. Set |request|["account_id"] to |accountId|. | |
1. Set |request|["client_id"] to |provider|'s {{IdentityProvider/clientId}}. | |
1. Set |request|["nonce"] to |provider|'s {{IdentityProvider/nonce}}. | |
1. Let |tokens| be the result of making a POST request described in | |
[[#idp-api-id-token-endpoint]]. | |
1. Return |tokens|["token"]. | |
</div> | |
<div algorithm> | |
The <dfn>fetch the manifest</dfn> algorithm accepts an {{IdentityProvider}} |provider| and returns a [=Manifest=] object: | |
1. In parallel, perform the following two steps: | |
1. Let |manifestInSet| be the result of running [=check the root manifest=], passing | |
|provider|. | |
1. Let |manifest| be the result of running [=fetch the internal manifest=], passing | |
|provider|. | |
1. If |manifestInSet| is true, return |manifest|, otherwise return null. | |
</div> | |
NOTE: We use a two-tier manifest list in order to prevent the [=IDP=] to easily determine the [=RP=] | |
that a user is visiting by encoding the information in the manifest path. We solve this issue by | |
requiring a manifest list to be on the root of the [=IDP=]. The manifest itself can be anywhere, but | |
it will not be used if the user agent does not find it in the manifest list. This allows the [=IDP=] | |
to keep their actual manifest on an arbitary path while allowing the user agent to prevent manifest | |
manipulation to fingerprint. See [[#manifest-fingerprinting]]. | |
<div algorithm> | |
The <dfn>check the root manifest</dfn> accepts an {{IdentityProvider}} |provider| and returns | |
whether the manifest is included in the manifest list: | |
1. Let |url| be |provider|'s {{IdentityProvider/configURL}}. | |
1. Let |rootUrl| be a new [=/URL=]. | |
1. Set |rootUrl|'s [=url/scheme=] to |url|'s [=url/scheme=]. | |
1. Set |rootUrl|'s [=url/host=] to |url|'s [=url/host=]'s [=host/registrable domain=]. | |
1. Set |rootUrl|'s [=url/path=] to the <a>list</a> «".well-known", "web-identity"». | |
1. Let |request| be a new <a spec=fetch for=/>request</a> whose [=request/url=] is |rootUrl| and | |
[=request/redirect mode=] set to "error". TODO: what other fields need to be set? | |
1. Let |check| be false. | |
1. [=Fetch=] |request| with <a spec=fetch>processResponseConsumeBody</a> set to the following | |
steps given a <a spec=fetch for=/>response</a> |response|: | |
1. If |response| is a [=network error=] or its [=response/status=] is not an [=ok status=], | |
return. | |
1. Let |mimeType| be the result of <a>extracting a MIME TYPE</a> from |response|'s | |
<a for=response>header list</a>. | |
1. If |mimeType| is failure or is not a [=JSON MIME Type=], return. | |
1. Let |json| be the result of [=parse JSON bytes to an Infra value=] passing |response|'s | |
[=response/body=]. | |
1. If |json| is a parsing exception, or if |json| is not an [=ordered map=], return. | |
1. If |json|["provider_urls"] does not exist, or if it is not a [=list=], return. | |
1. If the [=list/size=] of |json|["provider_urls"] is greater than 1, return. | |
Issue: [relax](https://github.com/fedidcg/FedCM/issues/333) the size of the provider_urls array. | |
1. If |json|["provider_urls"][0] is not a [=string=], return. | |
1. Set |check| to true if |json|["provider_urls"][0] [=string/is=] equal to |provider|'s | |
{{IdentityProvider/configURL}}. | |
1. Return |check|. | |
</div> | |
The <dfn>fetch the internal manifest</dfn> algorithm accepts an {{IdentityProvider}} |provider| and returns a [=Manifest=]: | |
1. Run a [[!CSP]] check with a [[CSP#directive-connect-src|connect-src]] | |
directive on the URL passed as |provider|'s {{IdentityProvider/configURL}} | |
and return if the check fails. | |
1. Return the result of fetching the |provider|'s {{IdentityProvider/configURL}} | |
and parsing it into a [=Manifest=]. TODO: specify how the fetching and the parsing works. | |
The fetch MUST be done with the [[#Sec-FedCM-CSRF|Sec-FedCM-CSRF]] header but without the | |
[=Identity Provider=]'s cookies. This request MUST NOT follow | |
[[RFC9110#header.location|HTTP redirects]] and instead throw an | |
error if there are any. | |
Issue: it is unclear if the [[#Sec-FedCM-CSRF|Sec-FedCM-CSRF]] header is [needed](https://github.com/fedidcg/FedCM/issues/267) on the manifest fetch. | |
<!-- ============================================================ --> | |
### IDP Sign-out ### {#browser-api-idp-sign-out} | |
<!-- ============================================================ --> | |
In enterprise scenarios, it is common for the user to want to clear all of | |
their existing sessions in all of the [=Relying Party=]s they are logged into. | |
It does so by being navigated to their [=Identity Provider=] who initiates | |
what's called a [[Front-Channel-Logout]]. | |
The browser exposes an API that takes the list of [=Relying Party=]s that the | |
[=Identity Provider=] wants to initiate the logout which are loaded in parallel | |
with cookies. | |
Each [=Relying Party=] endpoint is responsible for clearing its local state | |
(e.g. clearing cookies). | |
After the completion of this API, the user's session is cleared and will go | |
through an [[#use-cases-explicit-sign-in]] upon return. | |
<script type="text/typogram"> | |
.---------------------------------. .---------------------------------. | |
| .-----------------------------. | | .-----------------------------. | | |
| | "https://rp.example" | | | | "https://idp.example" | | | |
| '-----------------------------' | | '-----------------------------' | | |
| .-----------------------------. | | .-----------------------------. | | |
| | | | | | | | | |
| | Welcome to my website! | | | | John, we are logging out | | | |
| | | | | | of the relying parties. | | | |
| | | | | | | | | |
| +-----------------------------+ | | | +~~~~~~~~~~~~~~~~~~~~~~~+ | | | |
| | Sign-in to rp.example | | | | :"<script>" : | | | |
| | with idp.example? | | | | :" IdentityCredential. ": | | | |
| | | | | | :" logoutRPs([{ ": | | | |
| | .----. | | | | :" url: ... ": | | | |
| | | :) | "John Doe" | | --> | | :" accountId: ...": | | | |
| | '----' "[email protected]" | | | | :" }, { ": | | | |
| | | | | | :" ... ": | | | |
| | +-------------------------+ | | | | :" }]); ": | | | |
| | | Continue as John | | | | | :"<\/script>" : | | | |
| | +------------+------------+ | | | | +~~~~~~~~~~~+~~~~~~~~~~~+ | | | |
| '--------------|--------------' | | '--------------|--------------' | | |
'----------------|----------------' '----------------|----------------' | |
| | | |
| | | |
| +------------------------+ | +-------+ | |
| | | | | | | |
+------->| Registered RPs |-------+------->| Queue | | |
| | | | | |
+------------------------+ +-------+ | |
</script> | |
<div class=example> | |
```js | |
await IdentityCredential.logoutRPs([{ | |
url: "https://rp1.example", | |
accountId: "123" | |
}, { | |
url: "https://rpN.example", | |
accountId: "456" | |
}]); | |
``` | |
</div> | |
[=IDP=]s can call {{IdentityCredential/logoutRPs()}} to log the user out of the [=RP=]s they are | |
signed into. | |
<xmp class=idl> | |
dictionary IdentityCredentialLogoutRPsRequest { | |
required USVString url; | |
required USVString accountId; | |
}; | |
[Exposed=Window, SecureContext] | |
partial interface IdentityCredential { | |
static Promise<undefined> logoutRPs(sequence<IdentityCredentialLogoutRPsRequest> logoutRequests); | |
}; | |
</xmp> | |
<div algorithm=logoutRPs> | |
When the {{IdentityCredential/logoutRPs()}} method is invoked given a [=list=] of | |
{{IdentityCredentialLogoutRPsRequest}}s |logoutRequests|, the user agent MUST execute the following | |
steps: | |
1. Let |promise| be a new {{Promise}}. | |
1. For each |request| in |logoutRequests| | |
1. Let |rpOrigin| be [=this=]'s [=Document/origin=]. | |
1. Let |idpOrigin| be |request|'s {{IdentityCredentialLogoutRPsRequest/url}}'s [=origin=]. | |
1. Let |account| be |request|'s {{IdentityCredentialLogoutRPsRequest/accountId}}. | |
1. Let |triple| be (|rpOrigin|, |idpOrigin|, |account|). | |
1. If [=state machine map=][|triple|] does not exist, continue. | |
1. Let |accountState| be [=state machine map=][|triple|]. | |
1. If the |accountState|'s {{AccountState/registration state}} is [=unregistered=] or | |
|accountState|'s {{AccountState/allows logout}} is false, continue. | |
1. POST to the |request|'s {{IdentityCredentialLogoutRPsRequest/url}} with the | |
[=Relying Parties=] cookies. This request MUST NOT follow | |
[[RFC9110#header.location|HTTP redirects]] and instead abort the request. | |
1. Set the |accountState| {{AccountState/allows logout}} to false. | |
1. <a for=Promise>Resolve</a> |promise| with [undefined] and return |promise|. | |
</div> | |
<!-- ============================================================ --> | |
## Backwards Compatibility ## {#browser-api-backwards-compatibility} | |
<!-- ============================================================ --> | |
Note: go over how we are planning to deal with backwards compatibility. | |
<!-- ============================================================ --> | |
# Permissions Policy Integration # {#permissions-policy-integration} | |
<!-- ============================================================ --> | |
FedCM defines a [=policy-controlled feature=] identified by the string <code>"<dfn export>identity-credentials-get</dfn>"</code>. | |
Its [=default allowlist=] is `"self"`. | |
A {{Document}}’s [=Document/permissions policy=] determines whether any content | |
in that document is allowed to obtain a credential object using the [[#browser-api|Browser API]]. | |
Attempting to invoke <code><a idl for="CredentialsContainer" lt="get()">navigator.credentials.get({identity:..., ...})</a></code> | |
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-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 method=]s | |
modified by this specification do not have such access since they are invoked [=in parallel=] | |
by {{CredentialsContainer}}'s <a abstract-op>Request a `Credential`</a> abstract operation. | |
<!-- ============================================================ --> | |
# Security # {#security} | |
<!-- ============================================================ --> | |
This section provides a few of the security considerations for the FedCM API. Note that there is a | |
separate section for [[#privacy]] considerations. | |
<!-- ============================================================ --> | |
## Content Security Policy ## {#content-security-policy} | |
<!-- ============================================================ --> | |
The first fetches triggered by the FedCM API are the manifest list, which is public, and the | |
internal manifest. 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 malicious[=IDP=] could then attempt to trick the user. The protection | |
against this attack is the [[!CSP]] 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 [[!CSP]] of | |
the [=RP=], hence preventing the undesired FedCM UI from being shown. Since any subsequent fetches | |
are same origin with respect to the internal manifest or at least dependent on the contents of the | |
internal manifest, they do not require additional checks. | |
The non-same-origin fetches include, for example, the brand icon. The user agent does not perform a | |
[[!CSP]] 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. | |
<!-- ============================================================ --> | |
## Sec-FedCM-CSRF Header ## {#sec-fedcm-csrf-header} | |
<!-- ============================================================ --> | |
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 the [[#Sec-FedCM-CSRF]] header, a | |
[=forbidden header name=]. The header 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 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. | |
Issue: There is an ongoing investigation on whether we should use this header or CORS [here](https://github.com/fedidcg/FedCM/issues/320). | |
<!-- ============================================================ --> | |
## Browser Surface Impersonation ## {#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. | |
<!-- ============================================================ --> | |
# Privacy # {#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. | |
<!-- ============================================================ --> | |
## Principals ## {#privacy-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 [[#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=]. | |
1. [=Relying Party=]s ([=RP=]s) 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, [=RP=]s cannot necessarily be trusted to limit the user information it | |
collects or use that information in an acceptable way. | |
1. [=Identity Provider=]s ([=IDP=]s) 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. | |
1. 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 consent from 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 consents. 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 confirmation to do so. | |
1. It is the [=user agent=]'s responsibility to determine when the user has consented for the | |
[=IDP=] to communicate with the [=RP=] in order to provide identification for the user. | |
1. Once the [=user agent=] has determined that the user has provided consent 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. | |
<!-- ============================================================ --> | |
## Network requests ## {#network-requests} | |
<!-- ============================================================ --> | |
The FedCM API introduces the ability for a site to ask the browser to execute a few different | |
network requests, as shown in the [[#high-level-design]] section. 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: | |
<script type="text/typogram"> | |
┌──────────────────────────┬─────────┬───────────┬──────────┐ | |
│ Endpoint │ cookies │ client_id │ referrer │ | |
├──────────────────────────┼─────────┼───────────┼──────────┤ | |
│ manifest │ no │ no │ no │ | |
├──────────────────────────┼─────────┼───────────┼──────────┤ | |
│ accounts_endpoint │ yes │ no │ no │ | |
├──────────────────────────┼─────────┼───────────┼──────────┤ | |
│ token_endpoint │ yes │ yes │ yes │ | |
├──────────────────────────┼─────────┼───────────┼──────────┤ | |
│ client_metadata_endpoint │ no │ yes │ yes │ | |
└──────────────────────────┴─────────┴───────────┴──────────┘ | |
</script> | |
For fetches that are sent with cookies, we actually consider them equivalent to first-party fetches, | |
and hence include first-party cookies as if the resource was loaded as a first-party, e.g. | |
regardless of the | |
[SameSite](https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html#name-the-samesite-attribute-2) | |
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. | |
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. | |
* The manifest fetch can't be used to track users because it is performed without cookies, [=client | |
id=], or referrer. Thus, anyone could perform this fetch, and the information contained therein | |
is considered public. | |
* The accounts fetch can't be used to track users because it is performed with cookies from the | |
[=IDP=] but, importantly, without the [=client id=] or referrer. This in theory is a new power | |
that the [=RP=] gains that it would not have otherwise. Preventing too many of these fetches may | |
be important, but [=IDP=]s are already expected to protect against DoS attacks. In addition, the | |
user agent should only allow one FedCM flow per page at any given moment, immediately rejecting | |
any attempts to start another one. Since a FedCM flow can only be terminated when the user | |
interacts or after a long timer, the number of fetches performed is not a concern. The [=IDP=] | |
does learn a lot about the user from this fetch, but this is discussed in detail below. | |
* The client metadata fetch can't be used to track users too because it is performed without cookies | |
from the [=IDP=], albeit with a [=client id=] and a referrer. This allows the [=IDP=] to | |
communicate the relevant [=Privacy Policy=] and [=Terms of Service=] to the user agent, in case | |
they need to be displayed. Again, besides possible timing attacks described here, the [=RP=] | |
gains nothing from this fetch, and the [=RP=] could already perform this fetch if it wanted to | |
since it involves no cookies from the [=IDP=]. | |
* By design, the token fetch exposes the user at the website to the [=IDP=]: it is | |
peformed with cookies, [=client id=], and referrer. Because of that, it is gated on the user | |
interacting with the user agent UI, and enables the [=IDP=] to communicate to the [=RP=] the | |
information required to perform a federated signin/signup. It is not possible for the [=RP=] or | |
the [=IDP=] to force the token fetch to happen without user consent, as the user agent cannot be | |
spoofed or otherwise tricked. | |
<!-- ============================================================ --> | |
## Attack Scenarios ## {#attack-scenarios} | |
<!-- ============================================================ --> | |
This section describes the scenarios in which various agents might attempt to gain user information. | |
It considers the possibilities when: | |
* The [=RP=] is collecting information, | |
* The [=IDP=] is collecting information, or | |
* Both the [=RP=] and the [=IDP=] are colluding. | |
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 [[RFC7258]]. | |
<!-- ============================================================ --> | |
### Manifest Fingerprinting ### {#manifest-fingerprinting} | |
<!-- ============================================================ --> | |
Suppose that the FedCM API did not have a two-tier manifest (see the [=fetch the manifest=] | |
algorithm), and instead directly had a single manifest. This would introduce the following | |
fingerprinting attack: | |
<div class=example> | |
```js | |
// 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}\` | |
}] | |
} | |
}); | |
``` | |
</div> | |
NOTE: You can read more about the attack described | |
[here](https://github.com/fedidcg/FedCM/issues/230#issuecomment-1067029200). | |
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 consent from the user. Our | |
mitigation to this problem is to use a top level .well-known 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. | |
<!-- ============================================================ --> | |
### Timing Attacks ### {#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 consent. 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](https://github.com/fedidcg/FedCM/issues/230#issuecomment-1089040953). | |
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. | |
1. 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. | |
1. 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. | |
The following are mitigations under investigation. | |
<!-- ============================================================ --> | |
#### Heuristics #### {#timing-attack-heuristics} | |
<!-- ============================================================ --> | |
One mitigation under investigation is to try to use heuristics to limit the attack surface: | |
* Any [=RP=]s or [=IDP=]s observed to be using this API to compromise user privacy in a deceptive or | |
abusive manner could be explicitly blocked from using it or put behind an interstitial. | |
* The [=user agent=] could detect [=trackers=] by noting alleged [=IDP=]s that do either of the | |
following: | |
* Never show UI despite the FedCM API fetches being performed. | |
* Provide generic user identification, such as "Anonymous", or "Guest" or "Incognito" as the | |
user's name. | |
* Have a suspiciously low click-through rate (e.g. most users don't recognize the value in using | |
this [=IDP=]). | |
* The [=user agent=] could block the API or show a static interstitial after the user has already | |
expressed lack of interest in the API because they did at least one of the following: | |
* Repeatedly ignored the FedCM API in the past in a similar scenario. | |
* Provide user settings to disable the FedCM API. | |
* Pressed some [=user agent=] UI to close the FedCM API's request for consent. | |
* The [=user agent=] could gate the FedCM fetches on a user interaction so that the timing attack is | |
only possible once the user has expressed some interest in the API. Note that this may provide a | |
poor user experience for users that do want to use the API, and may also result in worse success | |
metrics for the API. | |
<!-- ============================================================ --> | |
#### Push Model #### {#push-model-mitigation} | |
<!-- ============================================================ --> | |
Another potential future mitigation for timing attacks is the [=push model=]. The current API is the | |
<dfn>pull model</dfn>: the API pulls the user accounts from the [=IDP=] every time they are needed | |
by the FedCM API. In the <dfn>push model</dfn>, the [=IDP=] needs to tell the [=user agent=] that a | |
user has signed in whenever this happens. This way, when the FedCM API is called, the [=user agent=] | |
already knows the user accounts that the user can select from, and thus does not require any | |
credentialed fetches to the [=IDP=] in order to show the UI. It would only be when the user | |
consents that the [=IDP=] is notified, thus resolving the timing attack problem entirely. | |
While the [=push model=] seems like an improvement for privacy, the current API uses the | |
[=pull model=] for these reasons: | |
* It introduces a lot of complexity for the [=IDP=]s, as they now need to declare the user accounts | |
and keep them updated all the time. In particular, they need to be updated when a user logs out, | |
deletes their account or simply change name, etc. A user can also use browser UX to logout, e.g. | |
by clearing cookies. While those are not high-frequency changes (e.g. once every other year for | |
each user), keeping them in sync is non-trivial. | |
* It requires a lot of the [=IDP=]'s trust in [=user agent=] to protect all of their user's | |
accounts. The push model requires storing all of the user accounts from all of the [=IDP=]s that | |
a user is logged into, regardless of whether the user ever uses the FedCM API or not. This means | |
that all users pay the cost, and only some get the reward. | |
<!-- ============================================================ --> | |
### IDP Intrusion ### {#idp-intrusion} | |
<!-- ============================================================ --> | |
> From [[PRIVACY-THREAT-MODEL#hl-intrusion]] | |
> | |
> Privacy harms don't always come from a site learning things. | |
> From [[RFC6973#section-5.1.3|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. | |
<!-- | |
<div class="image"> | |
<img src="img/mock45.svg"> | |
</div> | |
--> | |
<!-- ============================================================ --> | |
## Other Attack Scenarios ## {#other-attack-scenarios} | |
<!-- ============================================================ --> | |
The following are other attack scenarios considered but for which we do not **currently** have specific | |
mitigations in this API, but would ideally like to see addressed in future revisions of this API. This is usually because the attacks rely on the user already providing user | |
consent. Usually stopping such attacks would be very hard since the [=RP=] and [=IDP=] communication | |
has been established. In addition, such attacks would still be possible when using regular login | |
mechanisms. Still, they are worth mentioning as they showcase some risks users take when signing | |
into [=RP=]s. In some cases, future mitigations are discussed. | |
<!-- ============================================================ --> | |
### Cross-Site Correlation ### {#attack-scenarios-by-rp-cross-site-correlation} | |
<!-- ============================================================ --> | |
This attack happens when multiple [=RP=]s 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. | |
<!-- | |
<div class="image"> | |
<img src="img/mock3.svg"> | |
</div> | |
--> | |
<div class="example"> | |
1. User signs into RP1 (which sells jewelry) with an IDP, providing to RP1 | |
their email address [email protected] | |
1. User signs into RP2 (which sells houses) with an IDP, providing to RP2 | |
their email address [email protected] | |
1. User browses the collection of wedding rings in RP1. | |
1. Out of band, RP1 tells RP2 that [email protected] is shopping for wedding rings | |
1. User browses the housing inventory in RP2. | |
1. RP2 uses the fact that the user is shopping for wedding rings in RP1 to | |
advertise and filters their housing inventory. | |
1. User is surprised that RP2 knows that they are shopping for wedding rings. | |
</div> | |
The problem of [=RP=]s [=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 [=RP=]s. 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 [=RP=]s. | |
<!-- | |
<div class="image"> | |
<img src="img/mock37.svg"> | |
</div> | |
--> | |
<!-- ============================================================ --> | |
### Unauthorized Data Usage ### {#unauthorized-data-usage} | |
<!-- ============================================================ --> | |
Another attack is when the [=RP=] or [=IDP=] uses user information for purposes not authorized by | |
the user. When the user agrees to allow the [=IDP=] to provide information to the [=RP=], the | |
consent is specific to certain purposes, such as sign-in and personalization. For instance, the | |
[=RP=] might use that data for other purposes that the user would not expect and did not authorize, | |
such as selling email addresses to a spam list. Spamming risk can exist even when using | |
[=directed identifiers=]. | |
<!-- ============================================================ --> | |
### RP Fingerprinting ### {#rp-fingerprinting} | |
<!-- ============================================================ --> | |
This attack happens when the [=RP=] employs client state-based tracking to identify user. Any | |
API that exposes any kind of client state to the web risk becoming a vector for fingerprinting. The | |
purpose of this API is for the user to provide identification to the [=RP=]. And the user should be | |
able to rescind the access to that identification, for instance by logging out. However, a tracking | |
[=RP=] could keep state to detect the user that was previously logged in: | |
<!-- | |
<div class="image"> | |
<img src="img/mock5.svg"> | |
</div> | |
--> | |
<!-- ============================================================ --> | |
### Secondary Use ### {#secondary-use} | |
<!-- ============================================================ --> | |
Secondary use is the use of collected information about an individual without the individual's | |
consent for a purpose different from that for which the information was collected. This attack | |
happens when [=IDP=]s misuse the the information collected to enable sign-in for other | |
purposes. | |
Existing federation protocols require that the [=IDP=] know which service is requesting a [=token=] | |
in order to allow identity federation. Identity providers can use this fact to build profiles of | |
users across sites where the user has decided to use federation with the same account. This profile | |
could be used, for example, to serve targeted advertisements to those users browsing on sites that | |
the IDP controls. | |
This risk can exist even in the case where the [=IDP=] does not having pre-existing user account | |
information (for instance, if it is not a _bona fide_ IDP), because FedCM requests sent to the | |
[=IDP=] are credentialed. This is more likely to occur if the [=RP=] is colluding with the | |
[=IDP=] to enable tracking via [[#timing-attacks]]. | |
<!-- | |
<div class="image"> | |
<img src="img/mock23.svg"> | |
</div> | |
--> | |
<div class='example'> | |
1. User signs into RP1 (which sells jewelry) with an [=IDP=]. | |
1. User signs into RP2 (which sells houses) with the same [=IDP=]. | |
1. User navigates to the [=IDP=]. | |
1. Because the [=IDP=] knows that the user has an account with RP1 and RP2, the | |
[=IDP=] can show ads about vacations for honeymoons. | |
1. The user is surprised that their [=IDP=] is aware of their plans to get | |
married. | |
</div> | |
Preventing tracking of users by the [=IDP=] is difficult: the [=RP=] has to be coded into the | |
identity token for security reasons, such as token reuse and fraud and abuse prevention. That said, | |
there have been cryptographic schemes developed to blind the [=IDP=] to the [=RP=] while still | |
preventing token reuse(see Mozilla’s [personas](https://wiki.mozilla.org/Identity/Persona_AAR)). | |
<!-- | |
<div class="image"> | |
<img src="img/mock34.svg"> | |
</div> | |
--> | |
<!-- ====================================================================== --> | |
# Extensibility # {#extensibility} | |
<!-- ====================================================================== --> | |
Note: go over the extensibility mechanisms. | |
<!-- ====================================================================== --> | |
# Acknowledgements # {#acknowledgements} | |
<!-- ====================================================================== --> | |
Note: write down the Acknowledgements section. | |
<pre class="biblio"> | |
{ | |
"CM": { | |
"href": "https://w3c.github.io/webappsec-credential-management/", | |
"title": "Credential Management" | |
}, | |
"Front-Channel-Logout": { | |
"authors": [ "M. Jones" ], | |
"href": "https://openid.net/specs/openid-connect-frontchannel-1_0.html", | |
"title": "Front-Channel Logout" | |
}, | |
"Identity-Use-Cases-in-Browser-Catalog": { | |
"authors": [ "V. Bertocci", "G. Fletcher" ], | |
"href": "https://datatracker.ietf.org/doc/html/draft-bertocci-identity-in-browser-00", | |
"title": "Identity Use Cases in Browser Catalog" | |
}, | |
"JWT": { | |
"authors": [ "M. Jones", "J. Bradley", "N. Sakimura" ], | |
"href": "https://datatracker.ietf.org/doc/html/rfc7519", | |
"title": "JWT" | |
}, | |
"OIDC-Connect-Core": { | |
"href": "https://openid.net/specs/openid-connect-core-1_0.html", | |
"title": "OIDC Connect Core" | |
}, | |
"PERMISSIONS-POLICY": { | |
"href": "https://w3c.github.io/webappsec-permissions-policy", | |
"title": "Permissions Policy" | |
}, | |
"PRIVACY-MODEL": { | |
"href": "https://github.com/michaelkleber/privacy-model", | |
"title": "Privacy Model" | |
}, | |
"PRIVACY-THREAT-MODEL": { | |
"href": "https://w3cping.github.io/privacy-threat-model/", | |
"title": "Target Privacy Threat Model" | |
}, | |
"PSL-PROBLEMS": { | |
"authors": ["Ryan Sleevi"], | |
"href": "https://github.com/sleevi/psl-problems", | |
"title": "Public Suffix List Problems" | |
}, | |
"RFC7258": { | |
"href": "https://datatracker.ietf.org/doc/html/rfc7258", | |
"title": "Pervasive Monitoring Is an Attack" | |
}, | |
"SAML-Glossary": { | |
"href": "https://docs.oasis-open.org/security/saml/v2.0/saml-glossary-2.0-os.pdf", | |
"title": "SAML glossary" | |
}, | |
"Security-Origin-Confusion": { | |
"href": "https://w3c.github.io/webappsec-credential-management/#security-origin-confusion", | |
"title": "Security Origin Confusion" | |
} | |
} | |
</pre> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment