Las llaves de acceso hacen que las cuentas de usuario sean más seguras, sencillas y fáciles de usar.
Fecha de publicación: 12 de octubre de 2022; última actualización: 9 de abril de 2026
El uso de llaves de acceso mejora la seguridad, simplifica los ingresos y reemplaza las contraseñas. A diferencia de las contraseñas normales, que los usuarios deben recordar y escribir de forma manual, las llaves de acceso usan mecanismos de bloqueo de pantalla del dispositivo, como datos biométricos o PINs, y reducen los riesgos de phishing y robo de credenciales.
Las llaves de acceso se sincronizan en todos los dispositivos a través de proveedores de llaves de acceso, como el Administrador de contraseñas de Google y el llavero de iCloud.
Se debe crear una llave de acceso, almacenar la clave privada de forma segura en el proveedor de llaves de acceso junto con los metadatos necesarios y almacenar su clave pública en tu servidor para la autenticación. La clave privada emite una firma después de la verificación del usuario en el dominio válido, lo que hace que las llaves de acceso sean resistentes al phishing. La clave pública verifica la firma sin almacenar credenciales sensibles, lo que hace que las llaves de acceso sean resistentes al robo de credenciales.
Cómo funciona la creación de una llave de acceso
Antes de que un usuario pueda acceder con una llave de acceso, debes crearla, asociarla con una cuenta de usuario y almacenar su clave pública en tu servidor.
Podrías pedirles a los usuarios que creen una llave de acceso en una de las siguientes situaciones:
- Durante o después del registro
- Después de acceder
- Después de acceder con una llave de acceso desde otro dispositivo (es decir, el objeto
authenticatorAttachmentescross-platform) - En una página dedicada donde 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: Almacena los detalles de la cuenta del usuario, incluida la clave pública.
- Frontend: Se comunica con el navegador y recupera los datos necesarios del backend.
- Navegador: Ejecuta tu código JavaScript y se comunica con la API de WebAuthn.
- Proveedor de llaves de acceso: Crea y almacena la llave de acceso. Por lo general, se trata de un administrador de contraseñas, como el Administrador de contraseñas de Google, o una llave de seguridad.
Antes de crear una llave de acceso, asegúrate de que el sistema cumpla con los siguientes requisitos previos:
La cuenta de usuario se verifica a través de un método seguro (por ejemplo, correo electrónico, verificación telefónica o federación de identidades) en un período razonablemente corto.
El frontend y el backend pueden comunicarse de forma segura para intercambiar datos de credenciales.
El navegador admite WebAuthn y la creación de llaves de acceso.
En las siguientes secciones, te mostraremos cómo verificar la mayoría de ellos.
Una vez que el sistema cumple con estas condiciones, se lleva a cabo el siguiente proceso para crear una llave de acceso:
- El sistema activa el proceso de creación de la llave de acceso cuando el usuario inicia la acción (por ejemplo, cuando hace clic en el botón "Crear una llave de acceso" en su página de administración de llaves de acceso o después de finalizar el registro).
- El frontend solicita los datos de credenciales necesarios del backend, incluida la información del usuario, un desafío y los IDs de credenciales para evitar duplicados.
- El frontend llama a
navigator.credentials.create()para solicitar al proveedor de llaves de acceso del dispositivo que genere una llave de acceso con la información del backend. Ten en cuenta que esta llamada devuelve una promesa. - El dispositivo del usuario lo autentica con un método biométrico, un PIN o un patrón para crear la llave de acceso.
- El proveedor de llaves de acceso crea una llave de acceso y devuelve una credencial de clave pública al frontend, lo que resuelve la promesa.
- El frontend envía la credencial de clave pública generada al backend.
- El backend almacena la clave pública y otros datos importantes para la autenticación futura.
- El backend notifica al usuario (por ejemplo, por correo electrónico) para confirmar la creación de la llave de acceso y detectar posibles accesos no autorizados.
Este proceso garantiza un registro de llaves de acceso seguro y sin problemas para los usuarios.
Compatibilidades
La mayoría de los navegadores admiten WebAuthn, con algunas brechas menores. Consulta passkeys.dev para obtener detalles sobre la compatibilidad con navegadores y SO.
Cómo crear una llave de acceso nueva
Para crear una llave de acceso nueva, el frontend debe seguir este proceso:
- Comprueba la compatibilidad.
- Recupera información del backend.
- Llama a la API de WebAuthn para crear una llave de acceso.
- Envía la clave pública que se devolvió al backend.
- Guarda la credencial.
En las siguientes secciones, se muestra cómo hacerlo.
Comprueba la compatibilidad
Antes de mostrar el botón "Crear una llave de acceso nueva", el frontend debe verificar lo siguiente:
- El navegador admite WebAuthn con
PublicKeyCredential.
- El navegador admite la detección de capacidades con
PublicKeyCredential.getClientCapabilities().
El navegador admite la IU condicional de WebAuthn con
conditionalGet.El dispositivo admite un autenticador de plataforma (puede crear una llave de acceso y autenticarse en el dispositivo) con
passkeyPlatformAuthenticator.
En el siguiente fragmento de código, se muestra cómo puedes verificar la compatibilidad antes de mostrar las opciones relacionadas con la llave de acceso.
if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true &&
capabilities.passkeyPlatformAuthenticator === true) {
// The browser supports passkeys and the conditional UI.
}
}
En este ejemplo, el botón Crear una llave de acceso nueva solo debe mostrarse si se cumplen todas las condiciones.
Recupera información del backend
Cuando el usuario haga clic en el botón, recupera la información necesaria del backend para llamar a navigator.credentials.create().
En el siguiente fragmento de código, se muestra un objeto JSON con la información necesaria para llamar a navigator.credentials.create():
// Example `PublicKeyCredentialCreationOptions` contents
{
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,
}
}
Los pares clave-valor del objeto contienen la siguiente información:
challenge: Es un desafío generado por el servidor en ArrayBuffer para este registro.rp.id: Un ID de RP (ID de entidad de confianza), un dominio y un sitio web pueden especificar su dominio o un sufijo registrable. Por ejemplo, si el origen de un RP eshttps://login.example.com:1337, su ID puede serlogin.example.comoexample.com. Si el ID del RP se especifica comoexample.com, el usuario puede autenticarse enlogin.example.como en cualquier subdominio deexample.com. Consulta Permite la reutilización de llaves de acceso en tus sitios con solicitudes de origen relacionado para obtener más información.rp.name: Es el nombre del RP (usuario de confianza). Este parámetro está obsoleto en WebAuthn L3, pero se incluye por motivos de compatibilidad.user.id: Es un ID de usuario único en ArrayBuffer, que se genera cuando se crea la cuenta. Debe ser permanente, a diferencia de un nombre de usuario que puede editarse. El ID de usuario identifica una cuenta, pero no debe contener información de identificación personal (PII). Es probable que ya tengas un ID de usuario en tu sistema, pero, si es necesario, crea uno específicamente para las llaves de acceso y asegúrate de que no contenga PII.user.name: Es un identificador único de la cuenta que el usuario reconocerá, como su dirección de correo electrónico o nombre de usuario. Se mostrará en el selector de cuentas.user.displayName: 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 para incluir aquí, pasa una cadena vacía. Esto puede aparecer en el selector de cuentas según el navegador.pubKeyCredParams: Especifica los algoritmos de clave pública admitidos por el RP (usuario de confianza). Te recomendamos que lo configures 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.excludeCredentials: Es una lista de IDs de credenciales ya registradas. Evita que se registre dos veces el mismo dispositivo proporcionando una lista de IDs de credenciales ya registrados. El miembrotransports, si se proporciona, debe contener el resultado de la llamada agetTransports()durante el registro de cada credencial.authenticatorSelection.authenticatorAttachment: Establece este parámetro en"platform"junto conhint: ['client-device']si esta creación de llave de acceso es una actualización de una contraseña, por ejemplo, en una promoción después de acceder."platform"indica que el RP quiere un autenticador de plataforma (un autenticador integrado en el dispositivo de la plataforma) que no solicite, por ejemplo, insertar una llave de seguridad USB. El usuario tiene una opción más sencilla para crear una llave de acceso.authenticatorSelection.requireResidentKey: Establécelo en un valor booleanotrue. Una credencial detectable (clave residente) almacena la información del usuario en la llave de acceso y permite que los usuarios seleccionen la cuenta durante la autenticación.authenticatorSelection.userVerification: Indica si una verificación de usuario con 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 este parámetro en"preferred"o omite la propiedad.
Te recomendamos que construyas el objeto en el servidor, codifiques el ArrayBuffer con Base64URL y lo recuperes del frontend. De esta manera, puedes decodificar la carga útil con PublicKeyCredential.parseCreationOptionsFromJSON() y pasarla directamente a navigator.credentials.create().
En el siguiente fragmento de código, se muestra cómo puedes recuperar y decodificar la información necesaria para crear la llave de acceso.
// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');
// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...
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 devuelve una promesa y espera la interacción del usuario mostrando un diálogo modal.
// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
publicKey: options
});
Envía la credencial de clave pública que se devolvió al backend
Después de que se verifica al usuario con el bloqueo de pantalla del dispositivo, se crea una llave de acceso y se resuelve la promesa devolviendo un objeto PublicKeyCredential al frontend.
La promesa se puede rechazar por diferentes motivos. Puedes controlar estos errores verificando la propiedad name del objeto Error:
InvalidStateError: Ya existe una llave de acceso en el dispositivo. No se le mostrará ningún diálogo de error al usuario. El sitio no debería tratar esto como un error. El usuario quería que se registrara el dispositivo local, y así fue.NotAllowedError: El usuario canceló la operación.AbortError: Se anuló la operación.- Otras excepciones: Se produjo un error inesperado. El navegador muestra un diálogo de error al usuario.
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 coincidente se encuentra en el dispositivo después de la autenticación. Este valor se debe almacenar en la base de datos del backend.rawId: Es una versión de ArrayBuffer del ID de credencial.response.clientDataJSON: Un ArrayBuffer que codifica datos del cliente.response.attestationObject: Es un objeto de certificación codificado en ArrayBuffer. Contiene información importante, como un ID de RP, marcas y una clave pública.authenticatorAttachment: Devuelve"platform"cuando se crea esta credencial en un dispositivo compatible con llaves de acceso.type: Este campo siempre se establece en"public-key".
Codifica el objeto con el método .toJSON(), serialízalo con JSON.stringify() y, luego, envíalo al servidor.
...
// Encode and serialize the `PublicKeyCredential`.
const _result = credential.toJSON();
const result = JSON.stringify(_result);
// Encode and send the credential to the server for verification.
const response = await fetch('/webauthn/registerResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
...
Cómo guardar la credencial
Después de recibir la credencial de clave pública en el backend, te recomendamos que uses una biblioteca o solución del servidor en lugar de escribir tu propio código para procesar una credencial de clave pública.
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 las propiedades recomendadas para guardar:
- ID de credencial: Es el ID de credencial que se devolvió con la credencial de clave pública.
- Nombre de la credencial: Es el nombre de la credencial. Asigna un nombre según el proveedor de llaves de acceso que la creó, el cual se puede identificar según el AAGUID.
- ID de usuario: Es el ID de usuario que se usó para crear la llave de acceso.
- Clave pública: Es la clave pública que se devuelve con la credencial de clave pública. Esto es necesario para verificar una aserción de llave de acceso.
- Fecha y hora de creación: Registra la fecha y la hora de creación de la llave de acceso. Esto es útil para identificar la llave de acceso.
- Fecha y hora del último uso: Registra la última fecha y hora en que el usuario usó la llave de acceso para acceder. Esto es útil para determinar qué llave de acceso usó (o no usó) el usuario.
- AAGUID: Es un identificador único del proveedor de llaves de acceso.
- Marca de aptitud para la copia de seguridad: Es verdadero si el dispositivo es apto para la sincronización de llaves de acceso. Esta información ayuda a los usuarios a identificar las llaves de acceso que se pueden sincronizar y las que están vinculadas a un dispositivo (no se pueden sincronizar) en la página de administración de llaves de acceso.
Sigue las instrucciones más detalladas en Registro de llaves de acceso del servidor.
Indica si falla el registro
Si falla el registro de una llave de acceso, es posible que el usuario se confunda. Si hay una llave de acceso en el proveedor de llaves de acceso y está disponible para el usuario, pero la clave pública asociada no se almacena en el servidor, los intentos de acceso con la llave de acceso nunca tendrán éxito y será difícil solucionar el problema. Asegúrate de informarle al usuario si es así.
Para evitar esta situación, puedes indicar una llave de acceso desconocida al proveedor de llaves de acceso con la API de Signal.
Si llama a PublicKeyCredential.signalUnknownCredential() con un ID de RP y un ID de credencial, el RP puede informar al proveedor de claves de acceso que se quitó la credencial especificada o que no existe. Depende del proveedor de la llave de acceso cómo manejar este indicador, pero, si se admite, se espera que se quite la llave de acceso asociada.
// Detect authentication failure due to lack of the credential
if (response.status === 404) {
// Feature detection
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId: "example.com",
credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
});
} else {
// Encourage the user to delete the passkey from the password manager nevertheless.
...
}
}
Para obtener más información sobre la API de Signal, consulta Cómo mantener la coherencia entre las llaves de acceso y las credenciales de tu servidor con la API de Signal.
Envía una notificación al usuario
Enviar una notificación (como un correo electrónico) cuando se registra una llave de acceso ayuda a los usuarios a detectar el acceso no autorizado a la cuenta. Si un atacante crea una llave de acceso sin que el usuario lo sepa, la llave de acceso seguirá disponible para futuros abusos, incluso después de que se cambie la contraseña. La notificación alerta al usuario y ayuda a evitar esta situación.
Lista de tareas
- Verifica al usuario (preferentemente por correo electrónico o con un método seguro) antes de permitirle crear una llave de acceso.
- Evita la creación de llaves de acceso duplicadas para el mismo proveedor de llaves de acceso con
excludeCredentials. - Guarda el AAGUID para identificar al proveedor de la llave de acceso y nombrar la credencial para el usuario.
- Indica si falla un intento de registrar una llave de acceso con
PublicKeyCredential.signalUnknownCredential(). - Envía una notificación al usuario después de crear y registrar una llave de acceso para su cuenta.
Recursos
- Registro de llaves de acceso del servidor
- Documento de Apple: Authenticating a User Through a Web Service (Cómo autenticar a un usuario a través de un servicio web)
- Documento de Google: Accede sin contraseñas con llaves de acceso
Próximo paso: Accede con una llave de acceso usando el autocompletado del formulario.