Cualquier contraseña que acepte tu aplicación puede adivinarse, reutilizarse en otro sitio, escribirse en una página de inicio de sesión falsa o filtrarse desde una copia de seguridad de la base de datos. Las passkeys eliminan los cuatro problemas a la vez. El usuario inicia sesión con la huella dactilar, el rostro o el PIN que ya desbloquea el teléfono o el portátil, y no hay ningún secreto en tu servidor que merezca la pena robar.
En la visión general de los nuevos componentes de inicio de sesión las passkeys recibieron cinco puntos, y la entrada sobre SAML cubrió el inicio de sesión para empresas que gestionan su propio proveedor de identidad. Esta entrada es la parte sin contraseña: cómo TsgcWSAPIServer_WebAuthn registra passkeys, inicia sesión sin nombre de usuario, muestra las passkeys en la lista de autocompletado del navegador y guarda cada credencial en tu propia base de datos.
Qué es una passkey
Una passkey es un par de claves creado por el autenticador del usuario: Windows Hello, el llavero de iCloud, el Administrador de contraseñas de Google, un gestor de contraseñas o una llave de seguridad FIDO2. La clave privada nunca sale del autenticador. Tu servidor solo guarda la clave pública, así que una tabla de passkeys filtrada no permite iniciar sesión a nadie. Cada firma está vinculada al dominio de tu sitio, así que un dominio de phishing parecido no obtiene nada que le sirva. Eso es lo que aquí significa resistente al phishing.
En términos de WebAuthn, una passkey es una credencial detectable, también llamada resident key. El autenticador guarda el userHandle junto a la clave privada, y por eso el usuario puede iniciar sesión sin escribir un nombre de usuario y el navegador puede listar por sí solo las passkeys de tu sitio.
Solicitar credenciales detectables
WebAuthn necesita un contexto seguro, así que sirve tus páginas por https, o desde localhost mientras desarrollas, y define WebAuthnOptions.RelyingParty con el nombre de host que muestra el navegador. El residentKey que piden las opciones de registro procede de WebAuthnOptions.DefaultOptions.Registration.DiscoverableCredential:
waundcPreferred, el valor predeterminado. El autenticador crea una credencial detectable cuando puede.waundcRequired. Solo se aceptan credenciales detectables. Úsalo para passkeys e inicio de sesión sin nombre de usuario.waundcDiscouraged. El autenticador debería crear una credencial del lado del servidor, que necesita el nombre de usuario para iniciar sesión.
Una solicitud de registro concreta puede seguir anulando el valor predeterminado con el campo discoverable_credential, establecido en required, preferred o discouraged.
Iniciar sesión sin nombre de usuario
Solicita las opciones de autenticación con el nombre de usuario vacío. Las opciones entonces no llevan lista allowCredentials, el navegador muestra las passkeys que tiene guardadas para tu relying party y el usuario elige una. No hay nada que escribir ni nada que escribir mal.
Como esta vez el servidor no eligió la credencial, comprueba más cosas. La respuesta debe incluir el userHandle, el userHandle debe pertenecer a la credencial que firmó, y se rechaza una credencial que tu aplicación no conoce.
Passkeys en la lista de autocompletado
El autocompletado, o mediación condicional, coloca las passkeys de tu sitio en la lista de sugerencias del campo de nombre de usuario, junto a las contraseñas guardadas. Es la forma suave de hacer que los usuarios migren: la página de inicio de sesión sigue funcionando para quienes tienen contraseña, y quienes tienen una passkey la eligen de la lista.
- Añade
autocomplete="username webauthn"al campo de nombre de usuario. - Carga
/sgcWebAuthn.jsen la página. El componente lo sirve él mismo, desde el endpoint definido enEndpointsOptions.WebAuthn. - Cuando se carga la página, solicita las opciones sin nombre de usuario y llama a
startAuthentication(options, true). La promesa se resuelve cuando el usuario elige una passkey de la lista.
El autocompletado necesita un navegador con mediación condicional, lo que hoy significa las versiones actuales de Chrome, Edge y Safari. browserSupportsWebAuthnAutofill() devuelve False en los demás, así que mantén también un botón “Iniciar sesión con una passkey” en la página.
Varias passkeys, un userHandle
Los usuarios reales tienen más de una passkey: una en el portátil, otra en el teléfono, quizá una llave de seguridad para el día en que se pierdan las dos. Registra cada una con el mismo nombre de usuario. Cuando ese nombre de usuario ya tiene credenciales que el servidor conoce, el nuevo registro reutiliza su userHandle (user.id), de modo que todas las passkeys de la cuenta comparten un único userHandle y todas llevan al mismo usuario.
El servidor conoce las credenciales registradas mientras está en ejecución y las que añades con AddCredential. Cuando tus passkeys viven en una base de datos, añádelas al iniciar para que cada cuenta mantenga un único userHandle. Cuando un usuario sí escribe un nombre de usuario, las opciones de autenticación listan todas las passkeys de ese usuario en allowCredentials, y el autenticador usa la que tenga.
Guarda las passkeys en tu propia base de datos
El componente no persiste las credenciales. Tus passkeys pertenecen junto a tu tabla de usuarios, y cuatro eventos conectan ambas cosas:
OnWebAuthnRegistrationSuccessful. Guarda el nuevo registro de credencial, por ejemploaCredentialRecord.AsJSON, junto con suCredentialIdyUsername.OnWebAuthnAuthenticationOptionsRequest. Con un nombre de usuario, añade las passkeys de ese usuario aCredentialRecords. Sin nombre de usuario, no añadas nada.OnWebAuthnAuthenticationGetCredential. Se dispara cuando la credencial elegida por el navegador no está en la lista de la ceremonia, que es el caso de todo inicio de sesión sin nombre de usuario y de autocompletado. Busca la credencial, rellenaaCredentialRecordy defineFound.OnWebAuthnAuthenticationSuccessful. Guarda el nuevoSignCountyBackupStatedeaAuthentication.Credential.CredentialRecord, y a continuación crea la sesión.
Guardar una passkey y volver a cargarla son dos manejadores breves:
uses
sgcWebAuthn_Classes;
// registration: store the whole record, keyed by its credential id
procedure TForm1.WebAuthnWebAuthnRegistrationSuccessful(Sender: TObject;
const aRegistration: TsgcWebAuthn_Registration;
const aCredentialRecord: TsgcWebAuthn_CredentialRecord; var Accept: Boolean);
begin
DBInsertPasskey(aCredentialRecord.CredentialId, aCredentialRecord.Username,
aCredentialRecord.AsJSON);
end;
// usernameless and autofill sign-in: the browser chose the passkey,
// find it by its credential id and hand it back to the server
procedure TForm1.WebAuthnWebAuthnAuthenticationGetCredential(Sender: TObject;
const aCredentialId: string;
const aCredentialRecord: TsgcWebAuthn_CredentialRecord; var Found: Boolean);
var
vJSON: string;
begin
Found := DBFindPasskey(aCredentialId, vJSON);
if Found then
aCredentialRecord.ReadJSON(vJSON);
end;
DBInsertPasskey y DBFindPasskey representan tu propio código de acceso a datos. El registro que devuelves debe llevar el CredentialId solicitado, y el mismo Username cuando la ceremonia empezó con un nombre de usuario, o el inicio de sesión falla. Guarda también el UserId en el registro almacenado, porque el servidor lo compara con el userHandle que envía el autenticador. Los eventos se ejecutan en los hilos de conexión del servidor, así que protege los recursos compartidos igual que los proteges en cualquier otro punto del servidor.
Sincronizadas o vinculadas al dispositivo
Los indicadores en los datos del autenticador indican qué tipo de passkey has recibido, y el registro de credencial los guarda como BackupEligible (el indicador BE) y BackupState (el indicador BS):
- BackupEligible True, BackupState True. Una passkey sincronizada, respaldada por la plataforma o el gestor de contraseñas y disponible en los demás dispositivos del usuario.
- BackupEligible True, BackupState False. Una passkey multidispositivo que todavía no está respaldada.
- BackupEligible False, BackupState False. Una passkey vinculada al dispositivo, como una llave de seguridad o una clave TPM que nunca sale del dispositivo. Sugiere al usuario que registre una segunda passkey, porque perder ese dispositivo significa perder la credencial.
Tu política puede tratarlas de forma distinta, por ejemplo aceptando solo llaves de seguridad vinculadas al dispositivo en las cuentas de administrador. En cada inicio de sesión, el servidor rechaza una respuesta con el indicador BS activado y el BE desactivado, y una respuesta cuyo indicador BE difiera del BackupEligible almacenado, porque la elegibilidad de una credencial nunca cambia. El nuevo indicador BS se copia en BackupState, y OnWebAuthnAuthenticationSuccessful es el lugar donde guardarlo.
El contador de firmas y los autenticadores clonados
Algunos autenticadores incrementan un contador de firmas en cada inicio de sesión. Cuando el contador de la respuesta o el SignCount almacenado no es cero, el contador de la respuesta debe ser mayor que el valor almacenado. Si no lo es, el inicio de sesión se rechaza, porque dos autenticadores que responden con la misma clave es justo el aspecto de un autenticador clonado.
La comprobación solo funciona si guardas el contador después de cada inicio de sesión, y el valor almacenado únicamente avanza. Las passkeys sincronizadas suelen informar 0 siempre, lo que desactiva la comprobación para ellas. Si tienes autenticadores que no incrementan el contador de forma fiable, define WebAuthnOptions.AllowSignCountLessOrEqualStoredValue en True para aceptarlos. El valor almacenado sigue sin bajar nunca.
Prueba la demo
La demo Demos\26.Authentication\01.Passkeys es un relying party completo: un TsgcWebSocketHTTPServer con un TsgcWSAPIServer_WebAuthn conectado, que sirve una pequeña página de inicio de sesión en https://localhost:5443. Guarda cada passkey en un archivo passkeys.json propio, mediante los cuatro eventos anteriores.
- Compila la demo y mantén libcrypto-3.dll y libssl-3.dll junto al ejecutable. Están en la carpeta de la demo.
- Deja el host 127.0.0.1, el puerto 5443 y el relying party localhost, y luego haz clic en Start.
- Haz clic en Open Browser y acepta el certificado de prueba autofirmado.
- Escribe un nombre de usuario y haz clic en Register passkey. Registra una segunda passkey para el mismo nombre de usuario. El formulario lista todas las passkeys con su tipo, sincronizada o vinculada al dispositivo, y su contador de firmas.
- Haz clic en Sign in without user name y elige una passkey. El servidor la encuentra mediante
OnWebAuthnAuthenticationGetCredentialy el registro muestra el usuario. - Recarga la página y haz clic en el campo de nombre de usuario. Las passkeys aparecen en la lista de autocompletado. Elige una para iniciar sesión.
- Reinicia la aplicación. Las passkeys se cargan de nuevo desde passkeys.json, lo que demuestra que el almacenamiento pertenece a tu aplicación y no al componente.
Documentación
Dónde conseguirlo
TsgcWSAPIServer_WebAuthn está incluido en las ediciones Enterprise y All-Access de sgcWebSockets, para Delphi y C++ Builder, y el mismo componente forma parte de sgcWebSockets .NET. Si solo necesitas autenticación, el paquete sgcAuth lo incluye junto con los demás componentes de inicio de sesión. Lo encuentras en la paleta SGC Auth, y nada cambia en una aplicación existente hasta que lo colocas en un formulario.
Sigue leyendo
- Inicio de sesión en Delphi con Passkeys, SAML SSO, LDAP y TOTP 2FA
- Inicio de sesión único SAML en Delphi con Entra ID, Okta y AD FS
- WebAuthn, passkeys y el fin de las contraseñas
Míralo en vídeo
Hay un vídeo breve, “Passkeys in Delphi: passwordless login with WebAuthn”, en el canal de eSeGeCe. Muestra el código en el IDE, lo que indican los indicadores de respaldo y el contador de firmas, y la demo funcionando en https://localhost: un usuario registra dos passkeys y luego inicia sesión sin escribir un nombre de usuario.
¿Preguntas, comentarios o ayuda para añadir passkeys a tu página de inicio de sesión? Ponte en contacto. Recibirás respuesta de las personas que escribieron el código.
