Ogni password accettata dalla tua applicazione può essere indovinata, riusata su un altro sito, digitata in una pagina di accesso falsa o trapelare da un backup del database. Le passkey eliminano tutti e quattro i problemi in una volta. L'utente accede con l'impronta digitale, il volto o il PIN che già sblocca il telefono o il portatile, e sul tuo server non c'è più nessun segreto che valga la pena rubare.
Nella panoramica dei nuovi componenti di login le passkey hanno ricevuto cinque punti, e l'articolo su SAML ha trattato l'accesso per le aziende che gestiscono un proprio identity provider. Questo articolo è il lato senza password: come TsgcWSAPIServer_WebAuthn registra le passkey, fa accedere gli utenti senza nome utente, mostra le passkey nella lista di autocompletamento del browser e conserva ogni credenziale nel tuo database.
Cos'è una passkey
Una passkey è una coppia di chiavi creata dall'authenticator dell'utente: Windows Hello, il portachiavi di iCloud, Google Password Manager, un gestore di password o una chiave di sicurezza FIDO2. La chiave privata non lascia mai l'authenticator. Il tuo server conserva solo la chiave pubblica, quindi una tabella di passkey trapelata non permette a nessuno di accedere. Ogni firma è vincolata al dominio del tuo sito, quindi un dominio di phishing simile non ottiene nulla di utilizzabile. Questo è ciò che qui significa resistente al phishing.
In termini WebAuthn, una passkey è una credenziale rilevabile, chiamata anche resident key. L'authenticator conserva lo userHandle accanto alla chiave privata, ed è per questo che l'utente può accedere senza digitare un nome utente e il browser può elencare da solo le passkey del tuo sito.
Richiedere credenziali rilevabili
WebAuthn richiede un contesto sicuro, quindi servi le tue pagine tramite https, o da localhost durante lo sviluppo, e imposta WebAuthnOptions.RelyingParty sul nome host mostrato dal browser. Il residentKey richiesto dalle opzioni di registrazione proviene da WebAuthnOptions.DefaultOptions.Registration.DiscoverableCredential:
waundcPreferred, il valore predefinito. L'authenticator crea una credenziale rilevabile quando può.waundcRequired. Vengono accettate solo credenziali rilevabili. Usalo per le passkey e l'accesso senza nome utente.waundcDiscouraged. L'authenticator dovrebbe creare una credenziale lato server, che richiede il nome utente per accedere.
Una singola richiesta di registrazione può comunque sovrascrivere il valore predefinito con il campo discoverable_credential, impostato su required, preferred o discouraged.
Accedere senza nome utente
Richiedi le opzioni di autenticazione con il nome utente vuoto. Le opzioni non portano quindi alcuna lista allowCredentials, il browser mostra le passkey che possiede per il tuo relying party, e l'utente ne sceglie una. Non c'è nulla da digitare e nulla da digitare male.
Poiché stavolta il server non ha scelto la credenziale, effettua controlli più approfonditi. La risposta deve includere lo userHandle, lo userHandle deve appartenere alla credenziale che ha firmato, e una credenziale che la tua applicazione non conosce viene rifiutata.
Passkey nella lista di autocompletamento
L'autocompletamento, o conditional mediation, inserisce le passkey del tuo sito nella lista dei suggerimenti del campo nome utente, accanto alle password salvate. È il modo delicato di far passare gli utenti: la pagina di accesso continua a funzionare per chi ha una password, e chi ha una passkey la sceglie dalla lista.
- Aggiungi
autocomplete="username webauthn"al campo nome utente. - Carica
/sgcWebAuthn.jsnella pagina. Il componente lo serve da sé, dall'endpoint impostato inEndpointsOptions.WebAuthn. - Al caricamento della pagina, richiedi le opzioni senza nome utente e chiama
startAuthentication(options, true). La promise si risolve quando l'utente sceglie una passkey dalla lista.
L'autocompletamento richiede un browser con conditional mediation, il che oggi significa le versioni attuali di Chrome, Edge e Safari. browserSupportsWebAuthnAutofill() restituisce False negli altri, quindi mantieni anche un pulsante “Accedi con una passkey” sulla pagina.
Più passkey, uno userHandle
Gli utenti reali hanno più di una passkey: una sul portatile, una sul telefono, forse una chiave di sicurezza per il giorno in cui entrambe vanno perse. Registrale ciascuna con lo stesso nome utente. Quando quel nome utente ha già credenziali note al server, la nuova registrazione riusa il loro userHandle (user.id), così ogni passkey dell'account condivide un unico userHandle e tutte portano allo stesso utente.
Il server conosce le credenziali registrate mentre è in esecuzione e quelle che aggiungi con AddCredential. Quando le tue passkey vivono in un database, aggiungile all'avvio in modo che ogni account mantenga un unico userHandle. Quando un utente digita effettivamente un nome utente, le opzioni di autenticazione elencano ogni passkey di quell'utente in allowCredentials, e l'authenticator usa quella che possiede.
Conserva le passkey nel tuo database
Il componente non persiste le credenziali. Le tue passkey appartengono accanto alla tua tabella utenti, e quattro eventi collegano le due cose:
OnWebAuthnRegistrationSuccessful. Salva il nuovo record di credenziale, ad esempioaCredentialRecord.AsJSON, insieme al suoCredentialIdeUsername.OnWebAuthnAuthenticationOptionsRequest. Con un nome utente, aggiungi le passkey di quell'utente aCredentialRecords. Senza nome utente, non aggiungere nulla.OnWebAuthnAuthenticationGetCredential. Si attiva quando la credenziale scelta dal browser non è nella lista della ceremony, cosa che accade in ogni accesso senza nome utente e in autocompletamento. Cerca la credenziale, popolaaCredentialRecorde impostaFound.OnWebAuthnAuthenticationSuccessful. Salva il nuovoSignCounteBackupStatediaAuthentication.Credential.CredentialRecord, quindi crea la sessione.
Salvare una passkey e ricaricarla sono due gestori brevi:
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 e DBFindPasskey rappresentano il tuo codice di accesso ai dati. Il record che restituisci deve portare il CredentialId richiesto, e lo stesso Username quando la ceremony è iniziata con un nome utente, altrimenti l'accesso fallisce. Conserva anche lo UserId nel record salvato, perché il server lo confronta con lo userHandle inviato dall'authenticator. Gli eventi vengono eseguiti nei thread di connessione del server, quindi proteggi le risorse condivise come le proteggi in qualsiasi altro punto del server.
Sincronizzate o vincolate al dispositivo
I flag nei dati dell'authenticator indicano che tipo di passkey hai ricevuto, e il record di credenziale li conserva come BackupEligible (il flag BE) e BackupState (il flag BS):
- BackupEligible True, BackupState True. Una passkey sincronizzata, salvata dalla piattaforma o dal gestore di password e disponibile sugli altri dispositivi dell'utente.
- BackupEligible True, BackupState False. Una passkey multi-dispositivo non ancora salvata.
- BackupEligible False, BackupState False. Una passkey vincolata al dispositivo, come una chiave di sicurezza o una chiave TPM che non lascia mai il dispositivo. Suggerisci all'utente di registrare una seconda passkey, perché perdere quel dispositivo significa perdere la credenziale.
La tua policy può trattarle diversamente, ad esempio accettando solo chiavi di sicurezza vincolate al dispositivo sugli account amministratore. A ogni accesso il server rifiuta una risposta con il flag BS impostato e il flag BE non impostato, e una risposta il cui flag BE differisce dal BackupEligible salvato, perché l'eligibilità di una credenziale non cambia mai. Il nuovo flag BS viene copiato in BackupState, e OnWebAuthnAuthenticationSuccessful è il punto in cui salvarlo.
Il contatore di firme e gli authenticator clonati
Alcuni authenticator incrementano un contatore di firme a ogni accesso. Quando il contatore nella risposta o il SignCount salvato non è zero, il contatore nella risposta deve essere maggiore del valore salvato. Se non lo è, l'accesso viene rifiutato, perché due authenticator che rispondono con la stessa chiave è esattamente l'aspetto di un authenticator clonato.
Il controllo funziona solo se salvi il contatore dopo ogni accesso, e il valore salvato si muove sempre solo in avanti. Le passkey sincronizzate di solito riportano 0 ogni volta, il che disattiva il controllo per loro. Se hai authenticator che non incrementano il contatore in modo affidabile, imposta WebAuthnOptions.AllowSignCountLessOrEqualStoredValue su True per accettarli. Il valore salvato comunque non viene mai abbassato.
Prova la demo
La demo Demos\26.Authentication\01.Passkeys è un relying party completo: un TsgcWebSocketHTTPServer con un TsgcWSAPIServer_WebAuthn collegato, che serve una piccola pagina di accesso su https://localhost:5443. Conserva ogni passkey in un proprio file passkeys.json, tramite i quattro eventi sopra descritti.
- Compila la demo e tieni libcrypto-3.dll e libssl-3.dll accanto all'eseguibile. Si trovano nella cartella della demo.
- Lascia host 127.0.0.1, porta 5443 e relying party localhost, quindi fai clic su Start.
- Fai clic su Open Browser e accetta il certificato di test autofirmato.
- Digita un nome utente e fai clic su Register passkey. Registra una seconda passkey per lo stesso nome utente. Il modulo elenca ogni passkey con il suo tipo, sincronizzata o vincolata al dispositivo, e il suo contatore di firme.
- Fai clic su Sign in without user name e scegli una passkey. Il server la trova tramite
OnWebAuthnAuthenticationGetCredentiale il log mostra l'utente. - Ricarica la pagina e fai clic sul campo nome utente. Le passkey compaiono nella lista di autocompletamento. Scegline una per accedere.
- Riavvia l'applicazione. Le passkey vengono ricaricate da passkeys.json, il che dimostra che l'archiviazione appartiene alla tua applicazione e non al componente.
Documentazione
Dove trovarlo
TsgcWSAPIServer_WebAuthn è incluso nelle edizioni Enterprise e All-Access di sgcWebSockets, per Delphi e C++ Builder, e lo stesso componente fa parte di sgcWebSockets .NET. Se ti serve solo l'autenticazione, il pacchetto sgcAuth lo include insieme agli altri componenti di login. Lo trovi nella palette SGC Auth, e in un'applicazione esistente non cambia nulla finché non lo trascini su un form.
Continua a leggere
- Login Delphi con Passkey, SAML SSO, LDAP e TOTP 2FA
- Single Sign-On SAML in Delphi con Entra ID, Okta e AD FS
- WebAuthn, Passkey, e il End di Passwords
Guardalo in video
C'è un breve video, “Passkeys in Delphi: passwordless login with WebAuthn”, sul canale eSeGeCe. Mostra il codice nell'IDE, cosa indicano i flag di backup e il contatore di firme, e la demo in esecuzione su https://localhost: un utente registra due passkey, poi accede senza digitare un nome utente.
Domande, feedback o aiuto per aggiungere le passkey alla tua pagina di accesso? Contattaci. Riceverai una risposta dalle persone che hanno scritto il codice.
