FedCM updates: Login Status API, Error API, and Auto-selected Flag API

Chrome 120 is shipping the Login Status API for FedCM. The Login Status API (formerly known as IdP Sign-in Status API) allows websites, particularly identity providers, to signal to the browser when their users are logging in and out. This signal is used by FedCM to address a silent timing attack problem, and in doing so, allows FedCM to operate without third-party cookies altogether. This update addresses the last remaining backwards-incompatible changes we previously identified in the original Intent to Ship of FedCM, as part of our scope of work.

While the Login Status API improves the privacy property and usability, it's a backward-incompatible change once shipped. If you have an existing implementation of FedCM, make sure to update it using the following instructions.

Additionally, Chrome is shipping two new Federated Credential Management (FedCM) features:

  • Error API: Notify users when their sign-in attempt fails with a native UI based on the server response from the id assertion endpoint, if any.
  • Auto-Selected Flag API: Notify the identity provider (IdP) and relying party (RP) if a credential was automatically selected in the flow.

Login Status API

The Login Status API is a mechanism where a website, especially an IdP, informs the browser the user's login status on the IdP. With this API, the browser can reduce unnecessary requests to the IdP and mitigate potential timing attacks.

Inform the browser about the user's login status

IdPs can signal the user's login status to the browser by sending an HTTP header or by calling a JavaScript API when the user is signed in on the IdP or when the user is signed out from all their IdP accounts. For each IdP (identified by its config URL) the browser keeps a tri-state variable representing the login state with possible values logged-in, logged-out, and unknown. The default state is unknown.

To signal that the user is signed in, send an Set-Login: logged-in HTTP header in a top-level navigation or a same-origin subresource request:

Set-Login: logged-in

Alternatively, call the JavaScript API navigator.login.setStatus('logged-in') from the IdP origin:

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

These calls record the user's login status as logged-in. When the user's login status is set to logged-in, the RP calling FedCM makes requests to the IdP's accounts list endpoint and displays available accounts to the user in the FedCM dialog.

To signal that the user is signed out from all their accounts, send Set-Login: logged-out HTTP header in a top-level navigation or a same-origin subresource request:

Set-Login: logged-out

Alternatively, call the JavaScript API navigator.login.setStatus('logged-out') from the IdP origin:

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

These calls record the user's login status as logged-out. When the user's login status is logged-out, calling the FedCM silently fails without making a request to the IdP's accounts list endpoint.

The unknown status is set before the IdP sends a signal using the Login Status API. We introduced this status for a better transition, because a user may have already signed into the IdP when we ship this API. The IdP may not have a chance to signal this to the browser by the time FedCM is first invoked. In this case, we make a request to the IdP's accounts list endpoint and update the status based on the response from the accounts list endpoint:

  • If the endpoint returns a list of active accounts, update the status to logged-in and open the FedCM dialog to show those accounts.
  • If the endpoint returns no accounts, update the status to logged-out and fail the FedCM call.

What if the user session expires? Let the user sign in through a dynamic login flow!

Even though the IdP keeps informing the user's login status to the browser, the status could be out of sync, such as when the session expires. The browser tries to send a credentialed request to the accounts list endpoint when the login status is logged-in, but the server returns no accounts because the session is no longer available. In such a scenario, the browser can dynamically let the user sign in to the IdP through a dialog window.

The FedCM dialog displays a message suggesting a sign in, as shown in the following image.

A FedCM dialog suggesting to sign in to the IdP.
A FedCM dialog suggesting to sign in to the IdP.

When the user clicks the Continue button, the browser opens a dialog for the IdP's login page.

An example dialog.
An example dialog shown after clicking on the sign in to the IdP button.

The login page URL is specified with login_url as part of the IdP config file.

{
  "accounts_endpoint": "/auth/accounts",
  "client_metadata_endpoint": "/auth/metadata",
  "id_assertion_endpoint": "/auth/idtokens",
  "login_url": "/login"
  }
}

The dialog is a regular browser window that has first-party cookies. Whatever happens within the dialog is up to the IdP, and no window handles are available to make a cross-origin communication request to the RP page. After the user is signed in, the IdP should:

  • Send the Set-Login: logged-in header or call the navigator.login.setStatus("logged-in") API to inform the browser that the user has been signed in.
  • Call IdentityProvider.close() to close the dialog.
A user signs into an RP after signing in to the IdP using FedCM.
A user signs into an RP after signing in to the IdP using FedCM.

You can try the Login Status API behavior in our demo.

  1. Tap the Go to the IdP and sign in button.
  2. Sign in with an arbitrary account.
  3. Select Session Expired from Account Status dropdown.
  4. Press the Update personal info button.
  5. Tap the Visit the RP to try FedCM button.

You should be able to observe the login to the IdP through the module behavior.

Error API

When Chrome sends a request to the ID assertion endpoint (for example, when a user clicks the Continue as button on the FedCM UI or auto-reauthentication is triggered), the IdP may not be able to issue a token for legitimate reasons. For example, if the client is unauthorized, the server is temporarily unavailable, and so on. Currently, Chrome fails the request silently in case of such errors and only notifies the RP by rejecting the promise.

With the Error API, Chrome notifies the user by showing a native UI with the error information provided by the IdP.

A FedCM dialog showing the error message after the user's sign-in attempt fails. The string is associated with the error type.
A FedCM dialog showing the error message after the user's sign-in attempt fails. The string is associated with the error type.

IdP HTTP API

In the id_assertion_endpoint response, the IdP can return a token to the browser if it can be issued upon request. In this proposal, in case a token cannot be issued, the IdP can return an "error" response, which has two new optional fields:

  1. code
  2. url
// id_assertion_endpoint response
{
  "error": {
     "code": "access_denied",
     "url": "https://idp.example/error?type=access_denied"
  }
}

For code, the IdP can choose one of the known errors from the OAuth 2.0 specified error list [invalid_request, unauthorized_client, access_denied, server_error and temporarily_unavailable] or use any arbitrary string. If the latter, Chrome renders the error UI with a generic error message and pass the code to the RP.

For url, it identifies a human-readable web page with information about the error to provide additional information about the error to users. This field is useful to users because browsers cannot provide rich error messages in a native UI. For example, links for next steps, customer service contact information and so on. If a user wants to learn more about the error details and how to fix it, they could visit the provided page from the browser UI for more details. The URL must be of the same-site as the IdP configURL.

try {
  const cred = await navigator.credentials.get({
    identity: {
      providers: [
        {
          configURL: 'https://idp.example/manifest.json',
          clientId: '1234',
        },
      ],
    }
  });
} catch (e) {
  const code = e.code;
  const url = e.url;
}

Auto-Selected Flag API

mediation: optional is the default user mediation behavior in the Credential Management API and it triggers automatic re-authentication when possible. However, auto reauthentication may be unavailable due to reasons that only the browser knows; when it's unavailable the user may be prompted to sign in with explicit user mediation, which is a flow with different properties.

  • From an API caller's perspective, when they receive an ID token, they don't have visibility over whether it was an outcome of an auto reauthentication flow. That makes it hard for them to evaluate the API performance and improve UX accordingly.
  • From the IdP's perspective, they are equally unable to tell whether an auto reauthentication occurred or not for performance evaluation. In addition, whether an explicit user mediation was involved could help them support more security related features. For example, some users may prefer a higher security tier which requires explicit user mediation in authentication. If an IdP receives a token request without such mediation, they could handle the request differently. For example, return an error code such that the RP can call the FedCM API again with mediation: required.

Therefore, providing visibility of the auto reauthentication flow would be beneficial to developers.

With the Auto-selected Flag API, Chrome shares whether an explicit user permission was acquired by tapping on the Continue as button with both the IdP and RP, whenever auto reauthentication occurred or an explicit mediation occurred. Sharing only happens after user permission is granted for IdP/RP communication.

IdP sharing

To share the information to the IdP post user permission, Chrome includes is_auto_selected=true in the POST request sent to the id_assertion_endpoint:

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=Ct0D&disclosure_text_shown=true&is_auto_selected=true

RP sharing

The browser can share the information to the RP in isAutoSelected via IdentityCredential:

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

if (cred.isAutoSelected !== undefined) {
  const isAutoSelected = cred.isAutoSelected;
}

Engage and share feedback

If you have feedback or encounter any issues during testing, you can share them at crbug.com.

Photo by Girl with red hat on Unsplash