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 mejora la seguridad, simplifica los accesos y reemplaza las contraseñas. A diferencia de las contraseñas normales, que los usuarios deben recordar e ingresar manualmente, las llaves de acceso usan los mecanismos de bloqueo de pantalla del dispositivo, como los datos biométricos o los PIN, y reducen los riesgos de phishing y el robo de credenciales.
Las llaves de acceso se sincronizan en todos los dispositivos con 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 que almacene la clave privada de forma segura en el proveedor de llaves de acceso junto con los metadatos necesarios y su clave pública almacenada 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.
Puedes 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,
[authenticatorAttachment](https://web.dev/articles/passkey-form-autofill#authenticator-attachment)
escross-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: Almacena los detalles de la cuenta de 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, además, interactúa con la API de WebAuthn.
- Proveedor de llaves de acceso: Crea y almacena la llave de acceso. Por lo general, es 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 breve.
El frontend y el backend pueden comunicarse de forma segura para intercambiar datos de credenciales.
El navegador es compatible con WebAuthn y la creación de llaves de acceso.
En las siguientes secciones, te mostraremos cómo verificar la mayoría de ellas.
Una vez que el sistema cumple con estas condiciones, se produce el siguiente proceso para crear una llave de acceso:
- El sistema activa el proceso de creación de llaves de acceso cuando el usuario inicia la acción (por ejemplo, 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 al 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 solicitarle 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 muestra 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 muestra 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 proceso de registro de llaves de acceso seguro y sin problemas para los usuarios.
Compatibilidad
La mayoría de los navegadores son compatibles con WebAuthn, con algunas excepciones menores. Consulta passkeys.dev para obtener detalles sobre la compatibilidad con navegadores y SO.
Crea una llave de acceso nueva
Para crear una llave de acceso nueva, este es el proceso que debe seguir el frontend:
- Comprueba la compatibilidad.
- Recupera información del backend.
- Llama a la API de WebAuth para crear una llave de acceso.
- Envía la clave pública que se muestra al backend.
- Guarda la credencial.
En las siguientes secciones, se muestra cómo hacerlo.
Comprueba la compatibilidad
Antes de mostrar un botón "Crear una llave de acceso nueva", el frontend debe verificar lo siguiente:
- El navegador admite WebAuthn con
PublicKeyCredential
.
- El dispositivo admite un autenticador de plataforma (puede crear una llave de acceso y autenticarse con ella) con
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.
- El navegador admite la IU condicional de WebAuthn con
PublicKeyCredenital.isConditionalMediationAvailable()
.
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.
// 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
}
});
}
En este ejemplo, el botón Crear una llave de acceso nueva solo debe mostrarse si se cumplen todas las condiciones.
Cómo recuperar información del backend
Cuando el usuario haga clic en el botón, recupera la información requerida 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
: Un desafío generado por el servidor en ArrayBuffer para este registro.rp.id
: Un ID de RP (ID de grupo de confianza), un dominio y un sitio web pueden especificar su dominio o un sufijo que se pueda registrar. Por ejemplo, si el origen de un RP eshttps://login.example.com:1337
, su ID puede serlogin.example.com
oexample.com
. Si el ID del RP se especifica comoexample.com
, el usuario puede autenticarse enlogin.example.com
o en cualquier subdominio deexample.com
. Consulta Cómo permitir la reutilización de llaves de acceso en tus sitios con solicitudes de origen relacionadas para obtener más información.rp.name
: Es el nombre del RP (usuario de confianza). Esto dejó de estar disponible en WebAuthn L3, pero se incluye por motivos de compatibilidad.user.id
: 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 se puede editar. 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 para que no contenga PII.user.name
: Es 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 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 cuenta según el navegador.pubKeyCredParams
: especifica los algoritmos de clave pública admitidos por el RP (usuario de confianza). Te recomendamos configurarlos 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 los IDs de credenciales ya registrados. Evita que se registre el mismo dispositivo dos veces, ya que proporciona 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
: Configúralo como"platform"
junto conhint: ['client-device']
si esta creación de llaves 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 desea un autenticador de plataforma (un autenticador incorporado en el dispositivo de la plataforma) que no solicite, por ejemplo, que se inserte una llave de seguridad USB. El usuario tiene una opción más simple para crear una llave de acceso.authenticatorSelection.requireResidentKey
: Configúralo como untrue
booleano. Una credencial detectable (llave residente) almacena la información del usuario en la llave de acceso y les permite a los usuarios seleccionar la cuenta cuando se realiza la autenticación.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 esto en"preferred"
o omite la propiedad.
Te recomendamos que construyas el objeto en el servidor, codifiques el ArrayBuffer con Base64URL y lo recuperes desde el 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 muestra una promesa y espera a que el usuario interactúe para mostrar 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 muestra 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, 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 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 el dispositivo local se registrara, 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 le muestra al usuario un diálogo de error.
El objeto de credencial de clave pública contiene las siguientes propiedades:
id
: 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"
.
Codifica el objeto con el método .toJSON()
, sérialo 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
});
...
Guarda la credencial
Después de recibir la credencial de clave pública en el backend, te recomendamos que uses una biblioteca o una 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.
La siguiente lista incluye las propiedades recomendadas para guardar:
- ID de credencial: Es el ID de credencial que se muestra con la credencial de clave pública.
- Nombre de la credencial: Es el nombre de la credencial. Asócialo al proveedor de llaves de acceso que lo creó, que se puede identificar en función del AAGUID.
- ID de usuario: Es el ID de usuario que se usa para crear la llave de acceso.
- Clave pública: Es la clave pública que se muestra 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 la creación de la llave de acceso. Esto es útil para identificar la llave de acceso.
- Fecha y hora de uso más reciente: Registra la fecha y hora en que el usuario usó la llave de acceso para ingresar. Esto es útil para determinar qué llave de acceso usó (o no) el usuario.
- AAGUID: Es un identificador único del proveedor de llaves de acceso.
- Backup Eligibility flag: 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 sincronizables y las llaves de acceso vinculadas al dispositivo (no sincronizables) 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 el registro falla
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 se realizarán correctamente y será difícil solucionar el problema. Asegúrate de informarle al usuario si ese es el caso.
Para evitar esta condición, puedes indicar una llave de acceso desconocida al proveedor de llaves de acceso con la API de Signal.
Cuando se llama a PublicKeyCredential.signalUnknownCredential()
con un ID de RP y un ID de credencial, el RP puede informar al proveedor de llaves de acceso que la credencial especificada se quitó o no existe. Depende del proveedor de llaves de acceso cómo manejar este indicador, pero, si es compatible, 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, lee Cómo mantener la coherencia entre las llaves de acceso y las credenciales de tu servidor con la API de Signal.
Cómo enviar 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, esta seguirá disponible para futuros abusos, incluso después de que se cambie la contraseña. La notificación alerta al usuario y ayuda a evitarlo.
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 el proveedor de llaves de acceso y asignar un nombre a la credencial del usuario.
- Indica si un intento de registrar una llave de acceso falla 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: 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 mediante el autocompletado del formulario.