Crea una llave de acceso para los accesos sin contraseña

Las llaves de acceso hacen que las cuentas de usuario sean más seguras, sencillas y fáciles de usar.

El uso de llaves de acceso en lugar de contraseñas es una excelente manera para que los sitios web logren que sus cuentas de usuario sean más seguras, sencillas y fáciles de usar, y sin contraseñas. Con una llave de acceso, los usuarios pueden acceder a un sitio web o a una app con solo usar su huella dactilar, rostro o PIN del dispositivo.

Para hacerlo, se debe crear una llave de acceso, asociarla a una cuenta de usuario y almacenar la clave pública en tu servidor antes de que un usuario pueda acceder con ella.

Cómo funciona

Se puede solicitar a un usuario que cree una llave de acceso en una de las siguientes situaciones:

  • Cuando un usuario accede con una contraseña
  • Cuando un usuario accede con una llave de acceso desde otro dispositivo (es decir, authenticatorAttachment es cross-platform).
  • En una página dedicada en la que los usuarios pueden administrar sus llaves de acceso.

Para crear una llave de acceso, usa la API de WebAuthn.

Los cuatro componentes del flujo de registro de la llave de acceso son los siguientes:

  • Backend: Es el servidor de backend que contiene la base de datos de cuentas que almacena la clave pública y otros metadatos sobre la llave de acceso.
  • Frontend: Es tu frontend que se comunica con el navegador y envía solicitudes de recuperación al backend.
  • Navegador: Es el navegador del usuario que ejecuta tu código JavaScript.
  • Autenticador: Es el autenticador del usuario que crea y almacena la llave de acceso. Esto puede incluir el administrador de contraseñas en el mismo dispositivo que el navegador (por ejemplo, cuando se usa Windows Hello) o en otro dispositivo, como un teléfono.
Diagrama de registro de llaves de acceso

El proceso para agregar una llave de acceso nueva a una cuenta de usuario existente es el siguiente:

  1. Un usuario accede al sitio web.
  2. Una vez que el usuario accede, solicita crear una llave de acceso en el frontend, por ejemplo, presionando el botón "Create a passkey".
  3. El frontend solicita información al backend para crear una llave de acceso, como información del usuario, un desafío y los IDs de credenciales que se deben excluir.
  4. El frontend llama a navigator.credentials.create() para crear una llave de acceso. Esta llamada muestra una promesa.
  5. Se crea una llave de acceso después de que el usuario da su consentimiento con el bloqueo de pantalla del dispositivo. Se resuelve la promesa y se muestra una credencial de clave pública al frontend.
  6. El frontend envía la credencial de clave pública al backend y almacena el ID de la credencial y la clave pública asociada con la cuenta de usuario para autenticaciones futuras.

Compatibilidad

La mayoría de los navegadores son compatibles con WebAuthn, pero existen pequeñas brechas. Consulta Compatibilidad con dispositivos: passkeys.dev para obtener información sobre qué combinación de navegadores y sistemas operativos admiten la creación de una llave de acceso.

Crea una llave de acceso nueva

A continuación, se muestra cómo debe funcionar un frontend cuando se recibe una solicitud para crear una llave de acceso nueva.

Detección de atributos

Antes de mostrar un botón "Crear una llave de acceso nueva", verifica lo siguiente:

  • El navegador admite WebAuthn con PublicKeyCredential.

Navegadores compatibles

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Origen

  • El dispositivo admite un autenticador de plataforma (puede crear una llave de acceso y autenticarse con ella) con PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().

Navegadores compatibles

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Origen

Navegadores compatibles

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Origen

// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `​​isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
    PublicKeyCredential.​​isConditionalMediationAvailable) {  
  // Check if user verifying platform authenticator is available.  
  Promise.all([  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
    PublicKeyCredential.​​isConditionalMediationAvailable(),  
  ]).then(results => {  
    if (results.every(r => r === true)) {  
      // Display "Create a new passkey" button  
    }  
  });  
}  

Hasta que se cumplan todas las condiciones, las llaves de acceso no serán compatibles con este navegador. El botón "Crear una llave de acceso nueva" no debería mostrarse hasta ese momento.

Recupera información importante del backend

Cuando el usuario haga clic en el botón, recupera información importante para llamar a navigator.credentials.create() desde el backend:

  • challenge: Un desafío generado por el servidor en ArrayBuffer para este registro. Aunque es obligatorio, no se usa durante el registro, a menos que se realice una certificación, ya que corresponde a un tema avanzado que no se aborda en este codelab.
  • user.id: Es el ID único de un usuario. Este valor debe ser un ArrayBuffer que no incluya información de identificación personal, por ejemplo, direcciones de correo electrónico o nombres de usuario. Un valor aleatorio de 16 bytes generado por cuenta funcionará bien.
  • user.name: Este campo debe contener un identificador único para la cuenta que el usuario reconocerá, como su dirección de correo electrónico o nombre de usuario. Se mostrará en el selector de cuenta. (Si utilizas un nombre de usuario, usa el mismo valor que en la autenticación con contraseña).
  • user.displayName: Este campo es un nombre obligatorio y más fácil de usar para la cuenta. No es necesario que sea único, y puede ser el nombre que eligió el usuario. Si tu sitio no tiene un valor adecuado que puedas incluir aquí, pasa una cadena vacía. Esto puede aparecer en el selector de cuentas según el navegador.
  • excludeCredentials: Evita que se registre el mismo dispositivo, ya que proporciona una lista de IDs de credenciales ya registrados. El miembro transports, si se proporciona, debe contener el resultado de la llamada a getTransports() durante el registro de cada credencial.

Llama a la API de WebAuthn para crear una llave de acceso

Llama a navigator.credentials.create() para crear una llave de acceso nueva. La API muestra una promesa y espera a que el usuario interactúe para mostrar un diálogo modal.

Navegadores compatibles

  • Chrome: 60.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Origen

const publicKeyCredentialCreationOptions = {
  challenge: *****,
  rp: {
    name: "Example",
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },
  pubKeyCredParams: [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
};

const credential = await navigator.credentials.create({
  publicKey: publicKeyCredentialCreationOptions
});

// Encode and send the credential to the server for verification.  

Los parámetros que no se explicaron anteriormente son los siguientes:

  • rp.id: Un ID de RP es un dominio, y un sitio web puede especificar su dominio o un sufijo registrable. Por ejemplo, si el origen de un RP es https://login.example.com:1337, su ID puede ser login.example.com o example.com. Si el ID del RP se especifica como example.com, el usuario puede autenticarse en login.example.com o en cualquier subdominio de example.com.

  • rp.name: Es el nombre del RP.

  • pubKeyCredParams: En este campo, se especifican los algoritmos de clave pública admitidos del RP. Te recomendamos establecerlo en [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. De esta forma, se especifica la compatibilidad con ECDSA con P-256 y RSA PKCS#1, lo que proporciona una cobertura completa.

  • authenticatorSelection.authenticatorAttachment: Establece este valor en "platform" si la creación de la llave de acceso es una actualización de una contraseña, p.ej., en una promoción después de acceder. "platform" indica que el RP desea un autenticador de plataforma (un autenticador incorporado en el dispositivo de la plataforma) que no solicite insertar, por ejemplo, una llave de seguridad USB. El usuario tiene una opción más sencilla para crear una llave de acceso.

  • authenticatorSelection.requireResidentKey: Configúralo como un valor booleano "true". Una credencial detectable (clave residente) almacena información del usuario en la llave de acceso y permite que los usuarios seleccionen la cuenta cuando se realiza la autenticación. Obtén más información sobre las credenciales detectables en el análisis detallado de las credenciales detectables.

  • authenticatorSelection.userVerification: Indica si una verificación de usuario mediante el bloqueo de pantalla del dispositivo es "required", "preferred" o "discouraged". El valor predeterminado es "preferred", lo que significa que el autenticador puede omitir la verificación del usuario. Establece esta opción en "preferred" o omite la propiedad.

Envía la credencial de clave pública que se muestra al backend

Después de que el usuario da su consentimiento con el bloqueo de pantalla del dispositivo, se crea una llave de acceso y se resuelve la promesa, lo que muestra un objeto PublicKeyCredential al frontend.

La promesa se puede rechazar por diferentes motivos. Para controlar estos errores, verifica la propiedad name del objeto Error:

  • InvalidStateError: Ya existe una llave de acceso en el dispositivo. No se mostrará ningún diálogo de error al usuario, y el sitio no debe tratar esto como un error, ya que el usuario quería que el dispositivo local se registrara y lo hizo.
  • NotAllowedError: El usuario canceló la operación.
  • Otras excepciones: Se produjo un error inesperado. El navegador le muestra al usuario un diálogo de error.

El objeto de credencial de clave pública contiene las siguientes propiedades:

  • id: Es un ID codificado en Base64URL de la llave de acceso creada. Este ID ayuda al navegador a determinar si una llave de acceso coincide en el dispositivo después de la autenticación. Este valor se debe almacenar en la base de datos del backend.
  • rawId: Una versión de ArrayBuffer del ID de credencial.
  • response.clientDataJSON: Datos del cliente codificados en ArrayBuffer.
  • response.attestationObject: Un objeto de certificación con codificación ArrayBuffer. Contiene información importante, como un ID de RP, marcas y una clave pública.
  • authenticatorAttachment: Muestra "platform" cuando se crea esta credencial en un dispositivo compatible con la llave de acceso.
  • type: Este campo siempre se establece como "public-key".

Si usas una biblioteca para controlar el objeto de credencial de clave pública en el backend, te recomendamos que envíes todo el objeto al backend después de codificarlo parcialmente con base64url.

Guarda la credencial

Cuando recibas la credencial de clave pública en el backend, pásala a la biblioteca de FIDO para procesar el objeto.

Luego, puedes almacenar la información recuperada de la credencial en la base de datos para usarla en el futuro. En la siguiente lista, se incluyen algunas propiedades típicas para guardar:

  • ID de credencial (clave primaria)
  • ID de usuario
  • Clave pública

La credencial de clave pública también incluye la siguiente información que te recomendamos guardar en la base de datos:

Sigue las instrucciones más detalladas en Registro de llaves de acceso del servidor.

Para autenticar al usuario, consulta Accede con una llave de acceso mediante el autocompletado de formularios.

Recursos