From Chrome 126, developers can start running an origin trial for a bundle of desktop Federated Credential Management API (FedCM) features that enable some Authorization use cases. The bundle consists of the Continuation API and the Parameters API, which enable an OAuth authorization flow-like experience involving an identity provider (IdP)-provided permission dialog. The bundle also includes other changes such as the Fields API, Multiple configURLs, and Custom Account Labels. From Chrome 126, we are also introducing an origin trial for the Storage Access API (SAA) that auto-grants SAA requests if the user has successfully logged in using FedCM in the past.
Origin Trial: FedCM Continuation API bundle
The FedCM Continuation API bundle consists of multiple FedCM extensions:
Continuation API
You can check out a demo of the API on Glitch.
The Continuation API allows the IdP's ID assertion endpoint to optionally return a URL that FedCM will render to allow the user to continue a multi-step sign-in flow. This allows the IdP to request the user to grant the relying party (RP) permissions beyond what is possible in the existing FedCM UI, such as access to the user's server-side resources.
Typically, the ID assertion endpoint returns a token required for authentication.
{
"token": "***********"
}
However, with the Continuation API, the ID assertion
endpoint can
return a continue_on
property which includes an absolute path or a relative
path to the ID assertion endpoint.
{
// In the id_assertion_endpoint, instead of returning a typical
// "token" response, the IdP decides that it needs the user to
// continue on a pop-up window:
"continue_on": "/oauth/authorize?scope=..."
}
As soon as the browser receives the continue_on
response, a new popup window
is opened and navigates the user to the specified path.
After the user interacts with the page, for example granting further permission
to share extra information with the RP, the IdP page can call
IdentityProvider.resolve()
to resolve the original
navigator.credentials.get()
call and return a token as an argument.
document.getElementById('allow_btn').addEventListener('click', async () => {
let accessToken = await fetch('/generate_access_token.cgi');
// Closes the window and resolves the promise (that is still hanging
// in the relying party's renderer) with the value that is passed.
IdentityProvider.resolve(accessToken);
});
The browser will then close the popup by itself and return the token to the API caller.
If the user rejects the request, you can close the window by calling
IdentityProvider.close()
.
IdentityProvider.close();
If for some reason the user has changed their account in the popup (for example the IdP offers a "switch user" function, or in delegation cases), the resolve call takes an optional second argument allowing something like:
IdentityProvider.resolve(token, {accountId: '1234');
Parameters API
The Parameters API allows the RP to provide additional parameters to the ID assertion endpoint. With the Parameters API, RPs can pass additional parameters to the IdP to request permissions for resources beyond basic sign-in. The user would authorize these permissions through an IdP-controlled UX flow that is launched via the Continuation API.
To use the API, add parameters to the params
property as an object in the
navigator.credentials.get()
call.
let {token} = await navigator.credentials.get({
identity: {
providers: [{
clientId: '1234',
configURL: 'https://idp.example/fedcm.json',
// Key/value pairs that need to be passed from the
// RP to the IdP but that don't really play any role with
// the browser.
params: {
IDP_SPECIFIC_PARAM: '1',
foo: 'BAR',
ETC: 'MOAR',
scope: 'calendar.readonly photos.write',
}
},
}
});
The property names in the params
object are prepended with param_
. In the
above example, the params property contains IDP_SPECIFIC_PARAM
as '1'
, foo
as 'BAR'
, ETC
as 'MOAR'
and scope
as 'calendar.readonly photos.write'
.
This will be translated as
param_IDP_SPECIFIC_PARAM=1¶m_foo=BAR¶m_ETC=MOAR¶m_scope=calendar.readonly%20photos.write
in the HTTP body of the request:
POST /fedcm_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=false¶m_IDP_SPECIFIC_PARAM=1¶m_foo=BAR¶m_ETC=MOAR¶m_scope=calendar.readonly%20photos.write
Get permissions dynamically
In general, for users it is most helpful to request permissions when they are needed, rather than when the developer feels they are easiest to implement. For example, asking for permission to access a camera when the user is about to take a photo is preferred to asking for permission as soon as the user lands on the website. The same practice applies to server resources. Request permissions only when they're needed for the user. This is called "dynamic authorization".
To request authorization dynamically with FedCM, the IdP can:
- Call
navigator.credentials.get()
with required parameters the IdP can understand, such asscope
. - The ID assertion
endpoint
confirms the user is already signed in and responds with a
continue_on
URL. - The browser opens a popup window with the IdP’s permission page asking for additional permission that matches the requested scopes.
- Once authorized via
IdentityProvider.resolve()
by the IdP, the window is closed and the RP's originalnavigator.credentials.get()
call gets a relevant token or an authorization code so that the RP can exchange it with a proper access token.
Fields API
The Fields API allows the RP to declare account attributes to request from the IdP so that the browser can render a proper disclosure UI in the FedCM dialog; it's the IdP's responsibility to include the requested fields in the returned token. Consider this requesting a "basic profile" in OpenID Connect versus "scopes" in OAuth.
To use Fields API, add parameters to the fields
property as an array in the
navigator.credentials.get()
call. The fields can contain 'name'
, 'email'
and 'picture'
for now, but can be expanded to include more values in the
future.
A request with fields
would look like this:
let { token } = await navigator.credentials.get({
identity: {
providers: [{
fields: ['name', 'email', 'picture'],
clientId: '1234',
configURL: 'https://idp.example/fedcm.json',
params: {
scope: 'drive.readonly calendar.readonly',
}
},
}
mediation: 'optional',
});
The HTTP request to the ID assertion
endpoint
includes the RP-specified fields
parameter, with the disclosure_text_shown
parameter set as true
if this is not a returning user, and the fields that the
browser disclosed to the user in a disclosure_shown_for
parameter:
POST /id_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=true&fields=email,name,picture&disclosure_shown_for=email,name,picture
If the RP needs access to any additional data from the IdP, such as access to a
calendar, this should be handled with a custom parameter as mentioned above. The
IdP returns a continue_on
URL to request permission.
If fields
is an empty array, the request would look like this:
let { token } = await navigator.credentials.get({
identity: {
providers: [{
fields: [],
clientId: '1234',
configURL: 'https://idp.example/fedcm.json',
params: {
scope: 'drive.readonly calendar.readonly',
}
},
}
mediation: 'optional',
});
If fields
is an empty array, the user agent will skip the disclosure UI.
This is the case even if the response from the accounts
endpoint
does not contain a client ID that matches the RP in approved_clients
.
In this case, the disclosure_text_shown
sent to the ID assertion
endpoint is
false in the HTTP body:
POST /id_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=false
Multiple configURLs
Multiple configURLs allow IdPs
to accommodate multiple config files for an IdP, by specifying
accounts_endpoint
and login_url
in the well-known
file the same
as the config files.
If accounts_endpoint
and login_url
are added to the well-known file, the
provider_urls
are ignored so that the IdP can support multiple config files.
If they aren't, provider_urls
continue to take effect so that it is backward
compatible.
The well-known file that supports multiple configURLs can look like this:
{
"provider_urls": [ "https://idp.example/fedcm.json" ],
"accounts_endpoint": "https://idp.example/accounts",
"login_url": "https://idp.example/login"
}
This allows us to:
- Maintain backwards and forwards compatibility with existing well-known files and the earlier version of browsers that are already deployed in the wild.
- Have an arbitrary number of config files—as long as they all point to the
same
accounts_endpoint
andlogin_url
. - Have no opportunity for entropy to be added to the credentialed fetch request
made to the
accounts_endpoint
, since it has to be specified at the ".well-known" level.
Supporting multiple configURLs is optional and the existing FedCM implementations can stay the same.
Custom Account Labels
Custom Account Labels allow FedCM
IdPs to annotate accounts so that RPs can filter them by specifying the label in
a config file. Similar filtering has been possible using the Domain Hint
API and the Login
Hint API by specifying
them in the navigator.credentials.get()
call, but the Custom Account Labels
can filter users by specifying the config file, which is especially useful when
multiple configURLs are used. Custom Account Labels are
also different in that they are provided from the IdP server, as opposed to from
the RP, like login or domain hints.
Example
An IdP supports two configURLs for consumer and enterprise respectively. The
consumer config file has an 'consumer'
label, and the enterprise config file
has a 'enterprise'
label.
With such a setup, the well-known file includes accounts_endpoint
and
login_url
to allow multiple configURLs.
{
"provider_urls": [ "https://idp.example/fedcm.json" ],
"accounts_endpoint": "https://idp.example/accounts",
"login_url": "https://idp.example/login"
}
When the accounts_endpoint
is provided in the well-known file, the
provider_urls
are ignored. The RP can point directly at respective config
files in the navigator.credentials.get()
call.
The consumer config file is at https://idp.example/fedcm.json
which includes
accounts
property that specifies 'consumer'
using the include
.
{
"accounts_endpoint": "https://idp.example/accounts",
"client_metadata_endpoint": "/client_metadata",
"login_url": "https://idp.example/login",
"id_assertion_endpoint": "/assertion",
"accounts": {
"include": "consumer"
}
}
The enterprise config file is at https://idp.example/enterprise/fedcm.json
,
which includes the accounts
property that specifies 'enterprise'
using the
include
.
{
"accounts_endpoint": "https://idp.example/accounts",
"client_metadata_endpoint": "/enterprise/client_metadata",
"login_url": "https://idp.example/login",
"id_assertion_endpoint": "/assertion",
"accounts": {
"include": "enterprise"
}
}
The common IdP accounts
endpoint
(in this example https://idp.example/accounts
) returns a list of accounts that
includes a labels property with assigned labels
in an array for each account.
The following is an example response for a user who has two accounts. One is for
consumer and the other is for enterprise:
{
"accounts": [{
"id": "123",
"given_name": "John",
"name": "John Doe",
"email": "john_doe@idp.example",
"picture": "https://idp.example/profile/123",
"labels": ["consumer"]
}], [{
"id": "4567",
"given_name": "Jane",
"name": "Jane Doe",
"email": "jane_doe@idp.example",
"picture": "https://idp.example/profile/4567",
"labels": ["enterprise"]
}]
}
When an RP wants to allow 'enterprise'
users to sign in, they can specify the
'enterprise'
configURL 'https://idp.example/enterprise/fedcm.json'
in the
navigator.credentials.get()
call:
let { token } = await navigator.credentials.get({
identity: {
providers: [{
clientId: '1234',
nonce: '234234',
configURL: 'https://idp.example/enterprise/fedcm.json',
},
}
});
As a result, only the account ID of '4567'
is available for the user to sign
in. The account ID of '123'
is silently hidden by the browser so that the user
won't be provided with an account that's not supported by the IdP on this site.
Origin trial: FedCM as a trust signal for the Storage Access API
Chrome 126 is starting an origin trial of FedCM as a trust signal for the Storage Access API. With this change, a prior permission grant via FedCM becomes a valid reason to automatically approve a storage access request by the Storage Access APIs.
This is useful when an embedded iframe wants to access personalized resources: for example, if idp.example is embedded in rp.example and needs to show a personalized resource. If the browser restricts access to third-party cookies, even if the user is signed in to rp.example using idp.example with FedCM, the embedded idp.example iframe won't be able to request personalized resources because requests won't include third-party cookies.
To achieve this, idp.example needs to get a storage access permission via its iframe embedded on the website, and this can only be obtained through a permission prompt.
With FedCM as a trust signal for the Storage Access API, Storage Access API permission checks not only accept the permission grant that is given by a storage access prompt, but also the permission grant given by a FedCM prompt.
// In top-level rp.example:
// Ensure FedCM permission has been granted.
const cred = await navigator.credentials.get({
identity: {
providers: [{
configURL: 'https://idp.example/fedcm.json',
clientId: '123',
}],
},
mediation: 'optional',
});
// In an embedded IdP iframe:
// No user gesture is needed to call this, and the call will be auto-granted.
await document.requestStorageAccess();
// This returns `true`.
const hasAccess = await document.hasStorageAccess();
Once the user is signed in with FedCM, the permission is auto-granted as long as the FedCM authentication is active. This means once the user is disconnected, requesting permission will show a prompt.
Participate in the origin trial
You can try the FedCM Continuation API bundle locally by turning on a Chrome
flag
chrome://flags#fedcm-authz
on Chrome 126 or later. You can also try the FedCM
as a trust signal for the Storage Access API locally by turning on
#fedcm-with-storage-access-api
on Chrome 126 or later.
These features are also available as origin trials. Origin trials allow you to try new features and give feedback on their usability, practicality, and effectiveness. For more information, check out the Get started with origin trials.
To try the FedCM Continuation API bundle origin trial, create two origin trial tokens:
- Register for the trial. Embed the token to the IdP origin.
- Register for the origin trial with a third-party matching checkbox checked. Follow the instructions in Register a third-party origin trial for the RP to embed the token for the RP.
If you are interested in enabling the Continuation API along with the button flow, enable the Button Mode API origin trial as well:
- Register for the origin trial as a third party. Follow the instructions in Register a third-party origin trial for the RP to embed the token for the RP.
To try the FedCM as a trust signal for the Storage Access API origin trial:
- Register for the origin trial. Embed the token to the IdP origin.
The Continuation API bundle origin trial and the FedCM as a trust signal for the Storage Access API origin trial are available from Chrome 126.
Register a third-party origin trial for the RP
- Go to the origin trial registration page.
- Click the Register button and fill out the form to request a token.
- Enter the IdP's origin as Web Origin.
- Check Third-party matching to inject the token with JavaScript on other origins.
- Click Submit.
- Embed the issued token on a third-party website.
To embed the token on a third-party website, add the following code to the IdP's JavaScript library or SDK served from the IdP's origin.
const tokenElement = document.createElement('meta');
tokenElement.httpEquiv = 'origin-trial';
tokenElement.content = 'TOKEN_GOES_HERE';
document.head.appendChild(tokenElement);
Replace TOKEN_GOES_HERE
with your own token.