Passkeys y WebAuthn: El Futuro de la Autenticación Llegó
Hola HaWkers, probablemente ya viste la opción "Entrar con Passkey" apareciendo en más y más sitios. Esta semana, Telegram anunció soporte a passkeys, siguiendo una tendencia que está transformando cómo nos autenticamos en la web.
¿Aún dependes de contraseñas tradicionales? Vamos a entender por qué passkeys son consideradas el futuro de la autenticación y cómo implementarlas en tus aplicaciones.
Qué Son Passkeys y WebAuthn
Passkeys y WebAuthn no son tecnologías competidoras - son partes de un sistema que trabaja junto para permitir login sin contraseña de forma segura.
WebAuthn (Web Authentication)
WebAuthn es una API estándar desarrollada por FIDO Alliance y W3C que permite logins sin contraseñas tradicionales.
Características principales:
- Basado en criptografía de clave pública
- Estándar del W3C desde 2019
- Soportado por todos los navegadores modernos
- Componente central del proyecto FIDO2
Passkeys
Passkeys son credenciales de autenticación FIDO que permiten login usando el mismo proceso de desbloqueo del dispositivo.
Cómo funcionan:
- Usan biometría, PIN o patrón del dispositivo
- Sincronizados entre dispositivos vía cloud
- No requieren memorizar contraseñas
- Resistentes a phishing por diseño
Por Qué Passkeys Son Más Seguros
Contraseñas tradicionales tienen problemas fundamentales de seguridad que passkeys resuelven por diseño.
Problemas de las Contraseñas
Vulnerabilidades comunes:
| Problema | Contraseñas | Passkeys |
|---|---|---|
| Reutilización | Común | Imposible |
| Phishing | Vulnerable | Inmune |
| Fuga de base | Datos expuestos | Solo clave pública |
| Fuerza bruta | Posible | Imposible |
| Keyloggers | Vulnerable | Inmune |
Cómo Passkeys Protegen
1. Vinculadas al dominio:
Una passkey creada para tubanco.com no puede ser usada en tubanco.fake.com, aunque el usuario sea engañado.
2. Sin secreto compartido:
El servidor almacena solo la clave pública, que es inútil sin la clave privada que nunca sale del dispositivo.
3. Únicas por servicio:
Cada passkey es un par de claves único, entonces fuga de un servicio no afecta otros.
Cómo Funciona Técnicamente
Vamos a entender el flujo de registro y autenticación con WebAuthn.
Flujo de Registro
// 1. Servidor genera challenge y opciones
const publicKeyCredentialCreationOptions = {
challenge: new Uint8Array(32), // Challenge aleatorio del servidor
rp: {
name: "Mi Aplicación",
id: "misitio.com"
},
user: {
id: new Uint8Array(16), // ID único del usuario
name: "usuario@email.com",
displayName: "Nombre del Usuario"
},
pubKeyCredParams: [
{ alg: -7, type: "public-key" }, // ES256
{ alg: -257, type: "public-key" } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform",
residentKey: "required",
userVerification: "required"
},
timeout: 60000
};
// 2. Navegador solicita creación de credencial
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});
// 3. Enviar clave pública para el servidor almacenar
await fetch('/api/auth/register', {
method: 'POST',
body: JSON.stringify({
id: credential.id,
rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),
response: {
clientDataJSON: btoa(String.fromCharCode(
...new Uint8Array(credential.response.clientDataJSON)
)),
attestationObject: btoa(String.fromCharCode(
...new Uint8Array(credential.response.attestationObject)
))
}
})
});
Flujo de Autenticación
// 1. Servidor envía challenge
const publicKeyCredentialRequestOptions = {
challenge: new Uint8Array(32), // Challenge nuevo del servidor
rpId: "misitio.com",
allowCredentials: [], // Vacío para passkeys descubribles
userVerification: "required",
timeout: 60000
};
// 2. Navegador solicita autenticación
const assertion = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions
});
// 3. Enviar firma para verificación en el servidor
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({
id: assertion.id,
rawId: btoa(String.fromCharCode(...new Uint8Array(assertion.rawId))),
response: {
clientDataJSON: btoa(String.fromCharCode(
...new Uint8Array(assertion.response.clientDataJSON)
)),
authenticatorData: btoa(String.fromCharCode(
...new Uint8Array(assertion.response.authenticatorData)
)),
signature: btoa(String.fromCharCode(
...new Uint8Array(assertion.response.signature)
))
}
})
});Implementación Práctica
Para facilitar la implementación, existen bibliotecas que abstraen la complejidad.
SimpleWebAuthn (Recomendado)
// Frontend - @simplewebauthn/browser
import {
startRegistration,
startAuthentication
} from '@simplewebauthn/browser';
// Registro
const registrationOptions = await fetch('/api/webauthn/register-options')
.then(res => res.json());
const registration = await startRegistration(registrationOptions);
await fetch('/api/webauthn/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(registration)
});
// Login
const authOptions = await fetch('/api/webauthn/auth-options')
.then(res => res.json());
const authentication = await startAuthentication(authOptions);
await fetch('/api/webauthn/authenticate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(authentication)
});
Backend con Node.js
// Backend - @simplewebauthn/server
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
// Generar opciones de registro
app.get('/api/webauthn/register-options', async (req, res) => {
const options = await generateRegistrationOptions({
rpName: 'Mi Aplicación',
rpID: 'misitio.com',
userID: user.id,
userName: user.email,
attestationType: 'none',
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required'
}
});
// Guardar challenge en la sesión
req.session.challenge = options.challenge;
res.json(options);
});
// Verificar registro
app.post('/api/webauthn/register', async (req, res) => {
const verification = await verifyRegistrationResponse({
response: req.body,
expectedChallenge: req.session.challenge,
expectedOrigin: 'https://misitio.com',
expectedRPID: 'misitio.com'
});
if (verification.verified) {
// Guardar credencial en la base de datos
await saveCredential(user.id, verification.registrationInfo);
res.json({ success: true });
}
});Actualizaciones de 2025
El ecosistema de passkeys continúa evolucionando con mejoras significativas.
WebAuthn Level 3
La especificación WebAuthn Level 3 fue publicada como Working Draft en enero de 2025, trayendo:
- APIs mejoradas para creación y uso de passkeys
- Nuevos recursos de seguridad
- Mejor soporte cross-device
Directrices NIST
El NIST finalizará las Digital Identity Guidelines (SP 800-63-4) en julio de 2025:
- Passkeys reconocidas como "syncable authenticators"
- Pueden alcanzar Authenticator Assurance Level 2 (AAL2)
- MFA resistente a phishing exigido para agencias federales de EE.UU.
Portabilidad de Passkeys
La FIDO Alliance está desarrollando nuevos protocolos:
- CXP (Credential Exchange Protocol) - Transferencia segura entre proveedores
- CXF (Credential Exchange Format) - Formato estándar de intercambio
Plataformas con Soporte
Passkeys ya son soportadas por las principales plataformas.
Sincronización por Proveedor
| Proveedor | Sincronización | Plataformas |
|---|---|---|
| Apple | iCloud Keychain | iOS, macOS |
| Google Password Manager | Android, Chrome | |
| Microsoft | Windows Hello | Windows 10/11 |
| 1Password | Aplicación | Todas |
| Dashlane | Aplicación | Todas |
Adopción por Servicios
Servicios populares con soporte a passkeys:
- Microsoft
- Apple
- Amazon
- GitHub
- PayPal
- eBay
- Nintendo
- Telegram (noviembre 2025)
Consideraciones de Implementación
Al agregar passkeys a tu aplicación, considera estos puntos.
Fallback Necesario
No todos los dispositivos soportan passkeys. Mantén opciones alternativas:
// Verificar soporte del navegador
const supportsWebAuthn = () => {
return window.PublicKeyCredential !== undefined &&
typeof window.PublicKeyCredential === 'function';
};
// Verificar soporte a passkeys condicionales
const supportsConditionalUI = async () => {
if (!supportsWebAuthn()) return false;
return await PublicKeyCredential
.isConditionalMediationAvailable?.() ?? false;
};Consideraciones de UX
- Ofrecer passkeys como opción, no obligatorio
- Explicar beneficios para usuarios
- Permitir registro de múltiples passkeys
- Mantener método de recuperación
Conclusión
Passkeys representan la mayor evolución en autenticación web desde la popularización de contraseñas. Con soporte amplio de navegadores y sistemas operativos, resistencia intrínseca a phishing y experiencia de usuario superior, es el momento correcto para comenzar a implementar.
Para desarrolladores, bibliotecas como SimpleWebAuthn hacen la implementación accesible. Para usuarios, la experiencia es más simple y segura que contraseñas tradicionales.
Si quieres profundizar tus conocimientos en seguridad web, recomiendo que revises otro artículo: GitHub Actions Cambia Precios en 2026 donde vas a descubrir cómo proteger tus pipelines de CI/CD.
¡Vamos a por ello! 🦅
💻 Domina JavaScript de Verdad
El conocimiento que adquiriste en este artículo es solo el comienzo. Hay técnicas, patrones y prácticas que transforman desarrolladores principiantes en profesionales requeridos.
Invierte en Tu Futuro
Preparé un material completo para que domines JavaScript:
Formas de pago:
- 1x de R$9,90 sin intereses
- o R$9,90 al contado

