Passkeys y WebAuthn: guía de despliegue para apps
Las contraseñas son la peor parte de casi cualquier app. Los usuarios las reutilizan, las olvidan y se las roban con phishing, y tú, como operador, cargas con la responsabilidad de almacenarlas. Las passkeys son la respuesta de la industria, y en 2026 ya son la corriente principal: todos los navegadores y plataformas móviles importantes las soportan, las grandes apps de consumo las ofrecen por defecto y los gestores de contraseñas las sincronizan. Lo que la mayoría de los artículos se salta es la parte que decide si un despliegue tiene éxito: las decisiones de relying party que tomas una vez y con las que convives, la recuperación de cuentas, y cómo operar passkeys junto a contraseñas sin duplicar tu superficie de ataque. De eso trata este artículo.
Qué es realmente una passkey
Una passkey reemplaza la contraseña con un par de llaves criptográficas. Cuando un usuario se inscribe, su dispositivo genera un par: una llave privada que nunca sale del dispositivo (custodiada por el hardware seguro del teléfono y desbloqueada con Face ID, una huella o el PIN) y una llave pública que tú guardas asociada al usuario. Para iniciar sesión, tu servidor envía un desafío aleatorio, el dispositivo lo firma con la llave privada y tu servidor verifica la firma con la llave pública que tiene. Lo crucial es que no hay secreto compartido. Solo guardas una llave pública, que a un ladrón no le sirve de nada. No hay nada que filtrar en una brecha, nada que reutilizar entre sitios y nada que una página de login falsa pueda capturar.
La resistencia al phishing es estructural, no conductual. Una passkey queda ligada a tu dominio al crearse: el navegador registra a qué sitio pertenece y solo la ofrecerá en ese sitio. A un usuario que cae en un dominio parecido no se le pide que tenga cuidado; su dispositivo simplemente no tiene una credencial que coincida. El phishing, la forma más común de robar cuentas, deja de funcionar sin importar lo convincente que sea la página.
Una passkey no se puede phishear, reutilizar ni robar de tu base de datos, porque el único secreto nunca sale del dispositivo del usuario.
El flujo de WebAuthn, paso a paso
El estándar de base es WebAuthn, la mitad web de FIDO2, implementado de forma nativa por navegadores y sistemas operativos móviles. No implementas la criptografía tú mismo; una biblioteca de servidor mantenida se encarga de los desafíos y la verificación de firmas, y la plataforma se encarga del lado del dispositivo. Hay dos ceremonias cortas, y cada una es un viaje de ida y vuelta.
Registro. El servidor genera las opciones: un desafío aleatorio, tu id de relying party, el id y nombre visible del usuario, y tus preferencias para la credencial. El navegador se las entrega al autenticador de la plataforma, el usuario aprueba con su biometría y se crea un nuevo par de llaves. La llave pública, el id de la credencial y un contador de firmas regresan al servidor, que verifica el origen y el desafío y guarda el registro.
// Registro, lado servidor (pseudocódigo alrededor de una biblioteca WebAuthn)
const options = await webauthn.generateRegistrationOptions({
rpID: "example.com", // tu id de relying party
rpName: "Example",
userID: user.id, userName: user.email,
authenticatorSelection: {
residentKey: "required", // descubrible: permite iniciar sesión sin usuario
userVerification: "preferred",
},
excludeCredentials: user.passkeys.map((p) => ({ id: p.credentialId })),
});
session.challenge = options.challenge; // guárdalo en el servidor, de un solo uso y corta vida
// ...navegador: const cred = await navigator.credentials.create({ publicKey: options });
const verified = await webauthn.verifyRegistrationResponse({
response: cred, expectedChallenge: session.challenge,
expectedOrigin: "https://example.com", expectedRPID: "example.com",
});
await db.passkeys.insert({ userId: user.id, credentialId, publicKey, counter, transports });Autenticación. El servidor emite un desafío nuevo; el navegador le pide al autenticador que lo firme con una credencial de este dominio; el servidor busca la llave pública por el id de credencial, verifica la firma, comprueba el desafío y el origen, y abre una sesión. Si la credencial se creó como descubrible, el navegador puede listar las passkeys del usuario para tu sitio sin que pidas primero un nombre de usuario, que es el inicio de sesión de un toque que la gente ya espera. Las dos ceremonias tardan menos de un segundo y no hay nada que teclear.
Decisiones de relying party que tomas una sola vez
Un puñado de elecciones en la configuración moldea todo el sistema, y algunas son incómodas de cambiar después.
El id de relying party
El RP id es el dominio al que se liga la credencial, y cada passkey que creen tus usuarios queda atada a él para siempre. Debe ser tu dominio registrable o un subdominio suyo; no puedes usar un dominio distinto. La elección habitual es el dominio raíz (example.com) en lugar de app.example.com, para que las credenciales funcionen en cualquier subdominio que agregues. Elígelo antes de que exista la primera passkey, porque un cambio deja huérfana a cada credencial creada bajo el id anterior.
Credenciales descubribles
Pedir una llave residente o descubrible significa que el autenticador guarda el identificador del usuario junto a la llave privada, lo que permite iniciar sesión sin campo de usuario. Esa es la experiencia a la que se refiere la gente cuando dice que las passkeys son más rápidas. El costo es que las llaves de seguridad físicas tienen espacios limitados para credenciales descubribles; para una app de consumo es el valor por defecto correcto, y para una app empresarial que espera YubiKeys quizá prefieras marcarlo como preferido en lugar de requerido.
Verificación de usuario y atestación
La verificación de usuario es la comprobación biométrica o de PIN. “Preferida” es el valor sensato para la mayoría de los productos; “requerida” es para acciones de alto valor donde quieres prueba de que un humano desbloqueó el dispositivo. La atestación, que el autenticador demuestre de qué marca y modelo es, suena útil y casi nunca lo es en apps de consumo; no la pidas a menos que un regulador o una política empresarial exija hardware específico, porque requerirla rompe la sincronización y agrega una cadena de certificados que debes mantener.
Sincronizada o ligada al dispositivo
Las passkeys modernas se sincronizan a través del llavero de la plataforma (iCloud Keychain, Google Password Manager o un gestor de terceros), así que un teléfono nuevo recibe la credencial después de iniciar sesión en la cuenta de la plataforma. Esa sincronización es lo que convirtió una idea ingeniosa en algo que se puede lanzar a usuarios comunes sin una avalancha de bloqueos. También significa que la seguridad de la passkey ahora incluye la seguridad de esa cuenta en la nube. Para casi cualquier producto es un excelente intercambio. Para unos pocos, el paso de una transacción de alto valor en un banco o una consola de administración, quizá prefieras credenciales ligadas al dispositivo en llaves físicas, y la respuesta te dice cuál tipo recibiste mediante la bandera de respaldo elegible, así que puedes aplicar políticas por credencial.
La recuperación de cuentas es la parte difícil de verdad
Aquí va la verdad incómoda: la seguridad de tu inicio de sesión es la seguridad de su ruta de recuperación más débil. Una passkey a prueba de phishing protegida por un flujo de “¿perdiste el acceso? haz clic en este enlace por correo” es exactamente tan fuerte como el enlace por correo. Las passkeys no hacen la recuperación más difícil de lo que era con contraseñas; hacen imposible seguir ignorándola, porque el modo de fallo ya no es “la olvidé” sino “el dispositivo que la tenía ya no está”.
El enfoque por capas que funciona en la práctica:
- Fomenta más de una passkey. La sincronización cubre el teléfono perdido dentro de un ecosistema; una segunda passkey en una laptop, una llave de seguridad o el teléfono de la pareja cubre el cambio de ecosistema. Pídela después de la primera inscripción, no durante.
- Conserva un segundo factor fuerte para la ruta de recuperación: un correo verificado más un código TOTP, o un juego de códigos de recuperación de un solo uso generados al inscribirse y mostrados una sola vez. Un código de recuperación es una contraseña que el usuario no puede reutilizar ni entregar en un phishing, porque solo la teclea en tu página de recuperación.
- Haz lenta la ruta de recuperación a propósito. Una demora de un día con una notificación a cada otro canal que tenga el usuario (correo, dispositivo existente) convierte un robo silencioso en uno que el dueño real puede interrumpir. La recuperación rápida es cómoda; también es lo que quieren los atacantes.
- Nunca dejes que soporte “simplemente lo restablezca” sin una verificación de identidad definida. La ingeniería social sobre el personal de soporte es cómo se han robado de verdad cuentas sin contraseña de alto perfil.
Escribe el flujo de recuperación antes que el flujo de inscripción. Si el documento tiene una sola línea, el diseño no está terminado.
Desplegar junto a las contraseñas
No lo cambias todo de la noche a la mañana. Un despliegue sensato mantiene funcionando el inicio de sesión con contraseña o enlace mágico y agrega las passkeys como la mejor puerta, y después empuja. La secuencia que recomendamos:
- Agrega, no reemplaces. Un usuario con passkey puede seguir entrando con su contraseña hasta que decida lo contrario. Guarda las passkeys en su propia tabla, una fila por credencial, para que un usuario tenga varias y puedas revocar una sin tocar el resto.
- Inscribe después de un inicio de sesión exitoso, cuando el usuario ya está autenticado y de buen humor, no en la pantalla de registro donde cada paso extra cuesta conversiones. La invitación debe ser de una oración y se puede omitir.
- Usa la interfaz condicional. Los navegadores pueden ofrecer una passkey guardada dentro del campo de usuario común vía autocompletado, así que los usuarios que tienen una son reconocidos al instante y los que no, no ven ningún cambio. Es la mayor ganancia de usabilidad de todo el estándar y son unas pocas líneas de código.
- Mide y luego retira. Cuando un usuario entre con passkey repetidamente, ofrécele eliminar la contraseña. Solo después dejas de aceptar contraseñas para esa cuenta, y solo para esa cuenta. Un sistema con dos métodos de inicio de sesión activos es tan fuerte como el más débil, así que la meta es una transición por usuario, no un estado dual permanente.
El argumento de cumplimiento se escribe solo una vez que el sistema existe: puedes demostrar que nunca guardaste nada que un atacante pudiera usar, y una brecha de tu tabla de usuarios es una brecha de una lista de llaves públicas.
Apps nativas: qué cambia en iOS y Android
La misma credencial funciona en un navegador y en tu app nativa, que es la idea, pero la plomería cambia. En iOS usas el framework AuthenticationServices y, en Android, la API Credential Manager; en un stack multiplataforma hay paquetes mantenidos tanto para Flutter como para React Native que los envuelven. Lo que los equipos pasan por alto es la asociación de dominio. Una app nativa debe probar que tiene permiso de usar passkeys para tu RP id, y lo hace mediante un archivo que alojas en tu dominio: el Apple App Site Association con una entrada webcredentials, y el assetlinks.json de Android declarando el certificado de firma de tu app. Si esos archivos están mal, la plataforma se niega en silencio a ofrecer la passkey y el error se ve como si no pasara nada. Publícalos antes de que la primera compilación llegue a los testers, y recuerda que Android verifica la llave de firma, así que una compilación de depuración y una de producción son apps distintas hasta que ambas huellas estén listadas. Los usuarios en una laptop sin passkey sincronizada pueden entrar igual con su teléfono mediante un código QR y una comprobación de proximidad por Bluetooth, así que “la passkey está en el otro dispositivo” es un problema resuelto y no un ticket de soporte.
Errores comunes que vemos en revisiones
- Generar el desafío en el cliente, o reutilizar uno. Debe emitirlo el servidor, ser aleatorio, de un solo uso y de corta vida.
- Saltarse la verificación de origen, que es exactamente la comprobación que hace fallar al phishing.
- Ignorar el contador de firmas. Un contador que retrocede sugiere una credencial clonada; regístralo como mínimo.
- Ligar a un subdominio y querer la credencial en otro lado un año después.
- Requerir atestación en una app de consumo y preguntarse por qué falla la inscripción con passkeys sincronizadas.
- Construir una inscripción preciosa y ninguna recuperación, que es el modelo de amenazas entero cambiado de lugar.
En resumen
Las passkeys son más rápidas para los usuarios, inmunes al phishing y eliminan tu responsabilidad más peligrosa: las contraseñas almacenadas. Las herramientas están maduras, todas las plataformas lo soportan y los usuarios ya conocen el gesto de desbloquear su teléfono. Lo que decide un despliegue es la ingeniería aburrida alrededor: un id de relying party elegido una vez, una ruta de recuperación tan fuerte como la puerta principal, y una transición por usuario fuera de las contraseñas en lugar de un estado dual permanente. La autenticación es parte de cada plataforma web y app móvil que construimos, y si estás considerando una revisión de lo que ya tienes, para eso existe nuestra auditoría de software. Para conversar sobre el inicio de sesión sin contraseña en tu producto, escríbenos, o mira lo que hemos lanzado.
Preguntas frecuentes
¿Qué es una passkey y en qué se diferencia de una contraseña?
Una passkey es un par de llaves criptográficas. La llave privada se queda en el dispositivo del usuario, protegida por su hardware seguro y desbloqueada con biometría o PIN, y tu servidor guarda solo la llave pública. Para iniciar sesión, el servidor envía un desafío aleatorio, el dispositivo lo firma y el servidor verifica la firma. No hay secreto compartido, así que nada en tu base de datos le sirve a un ladrón, nada se puede reutilizar entre sitios y, como la credencial está ligada a tu dominio, un sitio de phishing parecido nunca la recibe.
¿Qué es el id de relying party y por qué importa?
El id de relying party es el dominio al que se liga cada passkey, y no se puede cambiar sin dejar huérfana a cada credencial creada con el anterior. Debe ser tu dominio registrable o un subdominio suyo. La mayoría de los productos elige el dominio raíz, por ejemplo example.com en lugar de app.example.com, para que las mismas credenciales funcionen en cualquier subdominio que se agregue después. Elígelo antes de que exista la primera passkey.
¿Qué pasa si un usuario pierde el dispositivo con su passkey?
Las passkeys sincronizadas sobreviven a un teléfono perdido dentro de un mismo ecosistema, porque iCloud Keychain, Google Password Manager o un gestor de terceros las restauran en el dispositivo nuevo. Más allá de eso, la ruta de recuperación es el verdadero trabajo de diseño: fomenta una segunda passkey en otro dispositivo, conserva un segundo factor fuerte como códigos de recuperación de un solo uso o un correo verificado más un código TOTP, haz lenta la recuperación a propósito con notificaciones a los otros canales del usuario, y nunca dejes que soporte restablezca el acceso sin una verificación de identidad definida. El inicio de sesión es tan fuerte como su ruta de recuperación más débil.
¿Puedo agregar passkeys sin eliminar las contraseñas?
Sí, y ese es el despliegue recomendado. Mantén el inicio de sesión con contraseña o enlace mágico, guarda las passkeys en su propia tabla con una fila por credencial, inscribe a los usuarios después de un inicio de sesión exitoso en lugar de en la pantalla de registro, y usa la interfaz condicional del navegador para que los usuarios con passkey sean reconocidos en el campo de usuario común. Cuando un usuario entre con passkey repetidamente, ofrécele eliminar la contraseña de esa cuenta. La meta es una transición por usuario, porque un sistema con dos métodos de inicio de sesión activos es tan fuerte como el más débil.
¿Las passkeys funcionan en apps nativas de iOS y Android?
Sí, y la misma credencial funciona en el navegador y en la app. En iOS usas el framework AuthenticationServices y en Android la API Credential Manager, con paquetes mantenidos disponibles para Flutter y React Native. El paso que los equipos pasan por alto es la asociación de dominio: la app debe probar que puede usar passkeys para tu id de relying party mediante un archivo Apple App Site Association con una entrada webcredentials y un assetlinks.json de Android con las huellas del certificado de firma de tu app. Si esos archivos están mal, la plataforma se niega en silencio a ofrecer la passkey.
¿Debería requerir atestación o verificación de usuario?
La verificación de usuario, la comprobación biométrica o de PIN, normalmente debe ser preferida y no requerida, reservando la requerida para acciones de alto valor donde quieres prueba de que un humano desbloqueó el dispositivo. La atestación, que demuestra la marca y el modelo del autenticador, rara vez es útil en apps de consumo, y requerirla rompe las passkeys sincronizadas y agrega una cadena de certificados que mantener. Pídela solo cuando un regulador o una política empresarial exija hardware específico.
¿Estás pensando en construir esto?
Appluex diseña y lanza apps móviles y web en producción, incluidas funciones de inteligencia artificial. Hablemos.