Actualizaciones de FedCM: Pruebas de origen para el paquete de API de Continuation y otorgamiento automático de la API de Storage Access

A partir de Chrome 126, los desarrolladores pueden comenzar a ejecutar una prueba de origen para un paquete de funciones de la API de Federated Credential Management (FedCM) para computadoras de escritorio que habilitan algunos casos de uso de autorización. El paquete consta de la API de Continuation y la API de Parameters, que habilitan una experiencia similar al flujo de autorización de OAuth que incluye un diálogo de permisos proporcionado por un proveedor de identidad (IdP). El paquete también incluye otros cambios, como la API de Fields, Multiple configURLs y Custom Account Labels. A partir de Chrome 126, también presentamos una prueba de origen para la API de acceso al almacenamiento (SAA) que otorga automáticamente las solicitudes de la SAA si el usuario accedió correctamente con FedCM en el pasado.

Prueba de origen: Paquete de la API de FedCM Continuation

El paquete de la API de Continuation FedCM consta de varias extensiones de FedCM:

API de Continuation

Un usuario accede a la RP y, luego, autoriza a través del modo de botón.

Puedes consultar una demo de la API en Glitch.

La API de Continuation permite que el extremo de aserción de ID del IdP muestre de forma opcional una URL que renderizará FedCM para permitir que el usuario continúe un flujo de acceso de varios pasos. Esto permite que la AC solicite al usuario que otorgue permisos a la parte de confianza (RP) más allá de lo que es posible en la IU de FedCM existente, como el acceso a los recursos del servidor del usuario.

Por lo general, el extremo de la aserción de ID muestra un token necesario para la autenticación.

{
  "token": "***********"
}

Sin embargo, con la API de Continuation, el extremo de la aserción de ID puede mostrar una propiedad continue_on que incluye una ruta de acceso absoluta o una ruta de acceso relativa al extremo de la aserción de ID.

{
  // 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=..."
}

En cuanto el navegador recibe la respuesta continue_on, se abre una nueva ventana emergente y se dirige al usuario a la ruta de acceso especificada.

Después de que el usuario interactúa con la página, por ejemplo, otorgando más permisos para compartir información adicional con la RP, la página del IdP puede llamar a IdentityProvider.resolve() para resolver la llamada navigator.credentials.get() original y mostrar un token como argumento.

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);
});

Luego, el navegador cerrará la ventana emergente por sí solo y devolverá el token al llamador de la API.

Si el usuario rechaza la solicitud, puedes llamar a IdentityProvider.close() para cerrar la ventana.

IdentityProvider.close();

Si, por algún motivo, el usuario cambió su cuenta en la ventana emergente (por ejemplo, el IdP ofrece una función "Cambiar de usuario" o en casos de delegación), la llamada de resolución toma un segundo argumento opcional que permite algo como lo siguiente:

IdentityProvider.resolve(token, {accountId: '1234');

API de Parameters

La API de Parameters permite que el RP proporcione parámetros adicionales al extremo de la aserción de ID. Con la API de Parameters, los RP pueden pasar parámetros adicionales al IdP para solicitar permisos para recursos más allá del acceso básico. El usuario autorizaría estos permisos a través de un flujo de UX controlado por la IdP que se inicia a través de la API de Continuation.

Para usar la API, agrega parámetros a la propiedad params como un objeto en la llamada a navigator.credentials.get().

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',
      }
    },
  }
});

Los nombres de las propiedades en el objeto params se anteponen con param_. En el ejemplo anterior, la propiedad params contiene IDP_SPECIFIC_PARAM como '1', foo como 'BAR', ETC como 'MOAR' y scope como 'calendar.readonly photos.write'. Esto se traducirá como param_IDP_SPECIFIC_PARAM=1&param_foo=BAR&param_ETC=MOAR&param_scope=calendar.readonly%20photos.write en el cuerpo HTTP de la solicitud:

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&param_IDP_SPECIFIC_PARAM=1&param_foo=BAR&param_ETC=MOAR&param_scope=calendar.readonly%20photos.write

Cómo obtener permisos de forma dinámica

En general, para los usuarios, es más útil solicitar permisos cuando se necesitan, en lugar de cuando el desarrollador considera que son más fáciles de implementar. Por ejemplo, es preferible solicitar permiso para acceder a una cámara cuando el usuario está a punto de tomar una foto en lugar de solicitarlo en cuanto llega al sitio web. La misma práctica se aplica a los recursos del servidor. Solicita permisos solo cuando sean necesarios para el usuario. Esto se denomina "autorización dinámica".

Para solicitar la autorización de forma dinámica con FedCM, el IdP puede hacer lo siguiente:

  1. Llama a navigator.credentials.get() con los parámetros obligatorios que la AC pueda interpretar, como scope.
  2. El extremo de aserción de ID confirma que el usuario ya accedió y responde con una URL de continue_on.
  3. El navegador abre una ventana emergente con la página de permisos del IdP que solicita un permiso adicional que coincida con los permisos solicitados.
  4. Una vez que la AC autoriza a través de IdentityProvider.resolve(), se cierra la ventana y la llamada navigator.credentials.get() original de la RP obtiene un token relevante o un código de autorización para que la RP pueda intercambiarlo por un token de acceso adecuado.

API de Fields

La API de Fields permite que el RP declare atributos de cuenta para solicitarlos al IdP de modo que el navegador pueda renderizar una IU de divulgación adecuada en el diálogo de FedCM. Es responsabilidad del IdP incluir los campos solicitados en el token que se muestra. Considera esto como solicitar un "perfil básico" en OpenID Connect en lugar de "alcances" en OAuth.

Mensaje de divulgación en modo de widget.
Mensaje de divulgación en el modo de widget.
Mensaje de divulgación en modo de botón.
Mensaje de divulgación en modo de botón.

Para usar la API de Fields, agrega parámetros a la propiedad fields como un array en la llamada a navigator.credentials.get(). Por ahora, los campos pueden contener 'name', 'email' y 'picture', pero se pueden expandir para incluir más valores en el futuro.

Una solicitud con fields se vería de la siguiente manera:

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',
});

La solicitud HTTP al extremo de la aserción de ID incluye el parámetro fields especificado por el RP, con el parámetro disclosure_text_shown establecido como true si no es un usuario recurrente, y los campos que el navegador divulgó al usuario en un parámetro disclosure_shown_for:

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

Si el RP necesita acceso a datos adicionales del IdP, como el acceso a un calendario, esto se debe controlar con un parámetro personalizado, como se mencionó anteriormente. El IdP muestra una URL de continue_on para solicitar permiso.

Si fields es un array vacío, la solicitud se verá de la siguiente manera:

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',
});

Si fields es un array vacío, el usuario-agente omitirá la IU de divulgación.

El mensaje de divulgación no se muestra en el modo de widget. En el flujo de botones, se omite por completo la IU de divulgación.
El mensaje de divulgación no se muestra en el modo de widget. En el flujo de botones, se omite por completo la IU de divulgación.

Este es el caso incluso si la respuesta del extremo de cuentas no contiene un ID de cliente que coincida con el RP en approved_clients.

En este caso, el disclosure_text_shown que se envía al extremo de aserción de ID es falso en el cuerpo HTTP:

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

Varios configURLs

Multiple configURLs permite que los IdP admitan varios archivos de configuración para un IdP, ya que especifican accounts_endpoint y login_url en el archivo conocido al igual que los archivos de configuración.

Si se agregan accounts_endpoint y login_url al archivo well-known, se ignoran los provider_urls para que el IdP pueda admitir varios archivos de configuración. Si no es así, provider_urls seguirá teniendo efecto para que sea retrocompatible.

El archivo well-known que admite varios configURLs puede verse de la siguiente manera:

{
  "provider_urls": [ "https://idp.example/fedcm.json" ],
  "accounts_endpoint": "https://idp.example/accounts",
  "login_url": "https://idp.example/login"
}

Esto nos permite lo siguiente:

  1. Mantén la retrocompatibilidad y la compatibilidad con versiones posteriores con los archivos conocidos existentes y la versión anterior de los navegadores que ya se implementaron en el campo.
  2. Tener una cantidad arbitraria de archivos de configuración, siempre que todos apunten al mismo accounts_endpoint y login_url
  3. No tienen la oportunidad de agregar entropía a la solicitud de recuperación de credenciales que se realiza a accounts_endpoint, ya que se debe especificar en el nivel “.well-known”.

La compatibilidad con varios configURLs es opcional, y las implementaciones existentes de FedCM pueden permanecer iguales.

Etiquetas de cuenta personalizadas

Las etiquetas de cuenta personalizadas permiten que las AC de FedCM anoten cuentas para que los RP puedan filtrarlas especificando la etiqueta en un archivo de configuración. Se pudo realizar un filtrado similar con la API de Domain Hint y la API de Login Hint especificando estos en la llamada navigator.credentials.get(), pero las etiquetas de cuenta personalizadas pueden filtrar a los usuarios especificando el archivo de configuración, lo que es especialmente útil cuando se usan multiple configURLs. Las etiquetas de cuenta personalizadas también son diferentes en cuanto a que se proporcionan desde el servidor de la IdP, en lugar de desde el RP, como las sugerencias de acceso o dominio.

Ejemplo

Un IdP admite dos configURLs para consumidores y empresas, respectivamente. El archivo de configuración del consumidor tiene una etiqueta 'consumer', y el archivo de configuración empresarial tiene una etiqueta 'enterprise'.

Con esa configuración, el archivo conocido incluye accounts_endpoint y login_url para permitir varios configURLs.

{
  "provider_urls": [ "https://idp.example/fedcm.json" ],
  "accounts_endpoint": "https://idp.example/accounts",
  "login_url": "https://idp.example/login"
}

Cuando se proporciona accounts_endpoint en el archivo well-known, se ignoran los provider_urls. El RP puede apuntar directamente a los archivos de configuración respectivos en la llamada a navigator.credentials.get().

El archivo de configuración del consumidor se encuentra en https://idp.example/fedcm.json, que incluye la propiedad accounts que especifica 'consumer' con 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"
  }
}

El archivo de configuración empresarial se encuentra en https://idp.example/enterprise/fedcm.json, que incluye la propiedad accounts que especifica 'enterprise' con 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"
  }
}

El endpoint de cuentas común del IdP (en este ejemplo, https://idp.example/accounts) muestra una lista de cuentas que incluye una propiedad de etiquetas con labels asignado en un array para cada cuenta. La siguiente es una respuesta de ejemplo para un usuario que tiene dos cuentas. Una es para consumidores y la otra para empresas:

{
 "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"]
  }]
}

Cuando un RP desea permitir que los usuarios de 'enterprise' accedan, puede especificar el 'https://idp.example/enterprise/fedcm.json' configURL de 'enterprise' en la llamada a navigator.credentials.get():

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

Como resultado, solo el ID de la cuenta de '4567' está disponible para que el usuario acceda. El navegador oculta de forma silenciosa el ID de la cuenta de '123' para que no se le proporcione al usuario una cuenta que no sea compatible con la AC en este sitio.

Prueba de origen: FedCM como indicador de confianza para la API de Storage Access

Chrome 126 inicia una prueba de origen de FedCM como indicador de confianza para la API de Storage Access. Con este cambio, un otorgamiento de permiso previo a través de FedCM se convierte en un motivo válido para que las APIs de acceso al almacenamiento aprueben automáticamente una solicitud de acceso al almacenamiento.

Esto es útil cuando un iframe incorporado quiere acceder a recursos personalizados: por ejemplo, si idp.example está incorporado en rp.example y necesita mostrar un recurso personalizado. Si el navegador restringe el acceso a las cookies de terceros, incluso si el usuario accedió a rp.example con idp.example con FedCM, el iframe idp.example incorporado no podrá solicitar recursos personalizados porque las solicitudes no incluirán cookies de terceros.

Para lograrlo, idp.example debe obtener un permiso de acceso al almacenamiento a través de su iframe incorporado en el sitio web, y esto solo se puede obtener a través de un mensaje de permiso.

Con FedCM como indicador de confianza para la API de Storage Access, las verificaciones de permisos de la API de Storage Access no solo aceptan la concesión de permisos que otorga un mensaje de acceso al almacenamiento, sino también la concesión de permisos que otorga un mensaje de FedCM.

// 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();

Una vez que el usuario accede con FedCM, el permiso se otorga automáticamente, siempre que la autenticación de FedCM esté activa. Esto significa que, una vez que el usuario se desconecte, se mostrará un mensaje para solicitar permiso.

Participa en la prueba de origen

Para probar el paquete de la API de Continuation de FedCM de forma local, activa una marca de Chrome chrome://flags#fedcm-authz en Chrome 126 o una versión posterior. También puedes probar FedCM como un indicador de confianza para la API de Storage Access de forma local. Para ello, activa #fedcm-with-storage-access-api en Chrome 126 o versiones posteriores.

Estas funciones también están disponibles como pruebas de origen. Las pruebas de origen te permiten probar funciones nuevas y enviar comentarios sobre su usabilidad, practicidad y eficacia. Para obtener más información, consulta Cómo comenzar a usar las pruebas de origen.

Para probar la prueba de origen del paquete de la API de FedCM Continuation, crea dos tokens de prueba de origen:

Si te interesa habilitar la API de Continuation junto con el flujo de botones, habilita también la prueba de origen de la API del modo de botones:

Para probar FedCM como un indicador de confianza para la prueba de origen de la API de Storage Access, haz lo siguiente:

La prueba de origen del paquete de la API de Continuation y FedCM como indicador de confianza para la prueba de origen de la API de Storage Access están disponibles a partir de la versión 126 de Chrome.

Registra una prueba de origen de terceros para la RP

  1. Ve a la página de registro de la prueba de origen.
  2. Haz clic en el botón Registrarse y completa el formulario para solicitar un token.
  3. Ingresa el origen del IdP como Origen web.
  4. Verifica la coincidencia de terceros para insertar el token con JavaScript en otros orígenes.
  5. Haz clic en Enviar.
  6. Incorpora el token emitido en un sitio web de terceros.

Para incorporar el token en un sitio web de terceros, agrega el siguiente código a la biblioteca de JavaScript o al SDK del proveedor de identidad que se publique desde el origen del proveedor de identidad.

const tokenElement = document.createElement('meta');
tokenElement.httpEquiv = 'origin-trial';
tokenElement.content = 'TOKEN_GOES_HERE';
document.head.appendChild(tokenElement);

Reemplaza TOKEN_GOES_HERE por tu propio token.