Las llaves de acceso permiten 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, simples, fáciles de usar y no tengan contraseña. Con una llave de acceso, un usuario puede acceder a un sitio web o una app con solo usar su huella dactilar, rostro o PIN del dispositivo.
Se debe crear una llave de acceso, asociarla a una cuenta de usuario y almacenar su clave pública en tu servidor antes de que un usuario pueda acceder con ella.
Cómo funcionan
Se puede pedir 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, el
authenticatorAttachment
escross-platform
). - En una página exclusiva 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 tu 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 el frontend que se comunica con el navegador y envía solicitudes de recuperación al backend.
- Navegador: El navegador del usuario que ejecuta tu 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 usas Windows Hello) o en otro dispositivo, como un teléfono.
![Diagrama de registro de la llave de acceso](https://web.dev/static/articles/passkey-registration/image/passkey-registration-diagram.png?authuser=5&hl=es)
El proceso para agregar una nueva llave de acceso a una cuenta de usuario existente es el siguiente:
- Un usuario accede al sitio web.
- Una vez que el usuario accede, solicita crear una llave de acceso en el frontend, por ejemplo, presionando el botón “Crear una llave de acceso”.
- El frontend solicita información al backend para crear una llave de acceso, como la información del usuario, un desafío y los IDs de credenciales que se excluirán.
- El frontend llama a
navigator.credentials.create()
para crear una llave de acceso. Esta llamada muestra una promesa. - Una llave de acceso se crea después de que el usuario otorga su consentimiento con el bloqueo de pantalla del dispositivo. La promesa se resuelve y se muestra una credencial de clave pública al frontend.
- El frontend envía la credencial de clave pública al backend y almacena el ID de credencial y la clave pública asociada con la cuenta de usuario para autenticaciones futuras.
Compatibilidades
WebAuthn es compatible con la mayoría de los navegadores, pero hay pequeñas brechas. Consulta Asistencia de dispositivos: llaves de acceso.dev para saber qué combinación de navegadores y sistemas operativos permite crear una llave de acceso.
Crea una nueva llave de acceso
A continuación, se muestra cómo debe funcionar un frontend cuando se solicita crear una llave de acceso nueva.
Detección de funciones
Antes de mostrar el botón "Crear una llave de acceso nueva", verifica 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()
.
// 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. Hasta entonces, no se debería mostrar el botón "Crear una llave de acceso nueva".
Cómo recuperar 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
: Es un desafío generado por el servidor en ArrayBuffer para este registro. Este campo es obligatorio, pero no se usa durante el registro, a menos que se realice una certificación, un tema avanzado que no se aborda aquí.user.id
: Es el ID único de un usuario. Este valor debe ser un ArrayBuffer que no incluya información de identificación personal, como 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. Esto se mostrará en el selector de cuentas. (Si usas 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 para incluir aquí, pasa una cadena vacía. Esto puede aparecer en el selector de cuentas según el navegador.excludeCredentials
: Impide el registro del mismo dispositivo, 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.
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, a la que espera la interacción del usuario para mostrar un diálogo modal.
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 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
.rp.name
: Es el nombre del RP.pubKeyCredParams
: Este campo especifica los algoritmos de clave pública compatibles con el RP. Te recomendamos configurarlos en[{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]
. Esto especifica la compatibilidad con ECDSA con P-256 y RSA PKCS#1, y la compatibilidad con estas funciones brinda una cobertura completa.authenticatorSelection.authenticatorAttachment
: Establece este parámetro en"platform"
si la creación de esta llave de acceso es una actualización de una contraseña, p.ej., en una promoción después de un acceso."platform"
indica que la parte restringida requiere un autenticador de plataforma (un autenticador incorporado en el dispositivo de la plataforma) que no solicitará su inserción, p.ej., 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 booleano "verdadero". Una credencial detectable (clave de residente) almacena información del usuario en la llave de acceso y permite que los usuarios seleccionen la cuenta cuando se autentica. Obtén más información sobre las credenciales detectables en 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. Configúralo como"preferred"
, o bien omite la propiedad.
Envía la credencial de clave pública que se muestra al backend
Después de que el usuario otorga su consentimiento con el bloqueo de pantalla del dispositivo, se crea una llave de acceso y se resuelve la promesa, y se 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 considerarlo como un error: el usuario quería que se registrara el dispositivo local y así es.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 debe almacenarse en la base de datos del backend.rawId
: Es una versión ArrayBuffer del ID de la credencial.response.clientDataJSON
: Datos de cliente codificados en ArrayBuffer.response.attestationObject
: Es un objeto de certificación con codificación ArrayBuffer. Esto 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 llaves de acceso.type
: Este campo siempre se establece en"public-key"
.
Si usas una biblioteca para controlar el objeto de credencial de clave pública en el backend, te recomendamos que envíes el objeto completo 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 FIDO para procesar el objeto.
Luego, puedes almacenar la información recuperada de la credencial en la base de datos para usarla más adelante. En la siguiente lista, se incluyen algunas propiedades típicas que se deben 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 puedes guardar en la base de datos:
- Marca de elegibilidad para la copia de seguridad:
true
si el dispositivo es apto para la sincronización de llaves de acceso. - Marca de estado de la copia de seguridad:
true
si la llave de acceso creada está configurada para sincronizarse. - Transportes: Una lista de transportes que admite el dispositivo.
"internal"
significa que el dispositivo admite una llave de acceso."hybrid"
significa que también admite la autenticación en otro dispositivo.
Sigue instrucciones más detalladas en Registro con llaves de acceso del servidor.
Para autenticar al usuario, consulta Accede con una llave de acceso mediante el autocompletado de formularios.