Passkeys in Delphi: wachtwoordloos aanmelden met WebAuthn

· Componenten
Passkeys in Delphi: wachtwoordloos aanmelden met WebAuthn

Elk wachtwoord dat uw applicatie accepteert, kan worden geraden, hergebruikt op een andere site, ingetypt op een nagemaakte aanmeldpagina of gelekt uit een back-up van de database. Passkeys lossen alle vier de problemen tegelijk op. De gebruiker meldt zich aan met de vingerafdruk, het gezicht of de pincode die de telefoon of laptop al ontgrendelt, en er staat op uw server geen geheim meer dat de moeite van het stelen waard is.

In het overzicht van de nieuwe login-componenten kregen passkeys vijf punten, en de SAML-post behandelde het aanmelden voor bedrijven met een eigen identity provider. Deze post is de wachtwoordloze kant: hoe TsgcWSAPIServer_WebAuthn passkeys registreert, gebruikers zonder gebruikersnaam aanmeldt, passkeys toont in de autofill-lijst van de browser en elke credential in uw eigen database bewaart.

Wat een passkey is

Een passkey is een sleutelpaar dat wordt gemaakt door de authenticator van de gebruiker: Windows Hello, iCloud-sleutelhanger, Google Wachtwoordmanager, een wachtwoordmanager of een FIDO2-beveiligingssleutel. De privésleutel verlaat de authenticator nooit. Uw server bewaart alleen de publieke sleutel, dus een gelekte tabel met passkeys laat niemand aanmelden. Elke handtekening is gebonden aan het domein van uw site, dus een sprekend gelijkend phishingdomein krijgt niets bruikbaars. Dat is wat phishingbestendig hier betekent.

In WebAuthn-termen is een passkey een detecteerbare credential, ook wel resident key genoemd. De authenticator bewaart de userHandle naast de privésleutel, en daarom kan de gebruiker aanmelden zonder een gebruikersnaam te typen en kan de browser de passkeys van uw site zelfstandig tonen.

Detecteerbare credentials aanvragen

WebAuthn heeft een secure context nodig, serveer uw pagina's dus via https, of vanaf localhost tijdens het ontwikkelen, en stel WebAuthnOptions.RelyingParty in op de hostnaam die de browser toont. De residentKey die de registratieopties vragen, komt uit WebAuthnOptions.DefaultOptions.Registration.DiscoverableCredential:

Een enkele registratieaanvraag kan de standaardwaarde nog steeds overschrijven met het veld discoverable_credential, ingesteld op required, preferred of discouraged.

Aanmelden zonder gebruikersnaam

Vraag de authenticatieopties op met een lege gebruikersnaam. De opties bevatten dan geen allowCredentials-lijst, de browser toont de passkeys die hij voor uw relying party heeft, en de gebruiker kiest er een. Er is niets om te typen en niets om verkeerd te typen.

Omdat de server deze keer niet de credential koos, controleert hij meer. Het antwoord moet de userHandle bevatten, de userHandle moet bij de credential horen die tekende, en een credential die uw applicatie niet kent, wordt afgewezen.

Passkeys in de autofill-lijst

Autofill, of conditional mediation, plaatst de passkeys van uw site in de suggestielijst van het gebruikersnaamveld, naast de opgeslagen wachtwoorden. Dat is de zachte manier om gebruikers te laten overstappen: de aanmeldpagina blijft werken voor mensen met een wachtwoord, en mensen met een passkey kiezen die uit de lijst.

Autofill vereist een browser met conditional mediation, wat vandaag de dag de actuele versies van Chrome, Edge en Safari betekent. browserSupportsWebAuthnAutofill() geeft in de andere browsers False terug, houd dus ook een knop “Aanmelden met een passkey” op de pagina beschikbaar.

Meerdere passkeys, één userHandle

Echte gebruikers hebben meer dan één passkey: een op de laptop, een op de telefoon, misschien een beveiligingssleutel voor de dag dat ze allebei kwijt zijn. Registreer elk daarvan met dezelfde gebruikersnaam. Wanneer die gebruikersnaam al credentials heeft die de server kent, hergebruikt de nieuwe registratie hun userHandle (user.id), zodat elke passkey van het account één userHandle deelt en ze allemaal naar dezelfde gebruiker leiden.

De server kent de credentials die zijn geregistreerd terwijl hij draait en de credentials die u toevoegt met AddCredential. Wanneer uw passkeys in een database leven, voegt u ze toe bij het opstarten, zodat elk account één userHandle behoudt. Wanneer een gebruiker wel een gebruikersnaam typt, vermelden de authenticatieopties elke passkey van die gebruiker in allowCredentials, en gebruikt de authenticator degene die hij heeft.

Bewaar de passkeys in uw eigen database

Het component bewaart geen credentials permanent. Uw passkeys horen naast uw gebruikerstabel, en vier events verbinden de twee:

Een passkey opslaan en weer laden zijn twee korte handlers:

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 en DBFindPasskey staan voor uw eigen data-toegangscode. Het record dat u teruggeeft, moet de gevraagde CredentialId bevatten, en dezelfde Username wanneer de ceremony met een gebruikersnaam begon, anders mislukt het aanmelden. Bewaar ook de UserId in het opgeslagen record, omdat de server die vergelijkt met de userHandle die de authenticator stuurt. De events lopen in de verbindingsthreads van de server, bescherm gedeelde resources dus zoals u ze overal elders in de server beschermt.

Gesynchroniseerd of apparaatgebonden

De vlaggen in de authenticatordata vertellen welk type passkey u hebt ontvangen, en het credentialrecord bewaart ze als BackupEligible (de BE-vlag) en BackupState (de BS-vlag):

Uw beleid kan ze verschillend behandelen, bijvoorbeeld door op beheerdersaccounts alleen apparaatgebonden beveiligingssleutels te accepteren. Bij elke aanmelding wijst de server een antwoord met de BS-vlag ingesteld en de BE-vlag niet ingesteld af, en een antwoord waarvan de BE-vlag afwijkt van de opgeslagen BackupEligible, omdat de eligibility van een credential nooit verandert. De nieuwe BS-vlag wordt gekopieerd naar BackupState, en OnWebAuthnAuthenticationSuccessful is de plek om die op te slaan.

De handtekeningteller en gekloonde authenticators

Sommige authenticators verhogen bij elke aanmelding een handtekeningteller. Wanneer de teller in het antwoord of de opgeslagen SignCount niet nul is, moet de teller in het antwoord groter zijn dan de opgeslagen waarde. Zo niet, dan wordt de aanmelding afgewezen, omdat twee authenticators die met dezelfde sleutel antwoorden, precies eruitzien als een gekloonde authenticator.

De controle werkt alleen als u de teller na elke aanmelding opslaat, en de opgeslagen waarde alleen maar vooruitgaat. Gesynchroniseerde passkeys melden meestal elke keer 0, wat de controle voor hen uitschakelt. Als u authenticators hebt die de teller niet betrouwbaar verhogen, stelt u WebAuthnOptions.AllowSignCountLessOrEqualStoredValue in op True om ze te accepteren. De opgeslagen waarde wordt nog steeds nooit verlaagd.

Probeer de demo

De demo Demos\26.Authentication\01.Passkeys is een complete relying party: een TsgcWebSocketHTTPServer met een gekoppelde TsgcWSAPIServer_WebAuthn, die een kleine aanmeldpagina serveert op https://localhost:5443. Het bewaart elke passkey in een eigen passkeys.json-bestand, via de vier events hierboven.

  1. Bouw de demo en houd libcrypto-3.dll en libssl-3.dll naast het uitvoerbare bestand. Ze staan in de demomap.
  2. Laat host 127.0.0.1, poort 5443 en relying party localhost staan, en klik dan op Start.
  3. Klik op Open Browser en accepteer het zelfondertekende testcertificaat.
  4. Typ een gebruikersnaam en klik op Register passkey. Registreer een tweede passkey voor dezelfde gebruikersnaam. Het formulier toont elke passkey met het type, gesynchroniseerd of apparaatgebonden, en de handtekeningteller.
  5. Klik op Sign in without user name en kies een passkey. De server vindt deze via OnWebAuthnAuthenticationGetCredential en het log toont de gebruiker.
  6. Herlaad de pagina en klik op het gebruikersnaamveld. De passkeys verschijnen in de autofill-lijst. Kies er een om aan te melden.
  7. Herstart de applicatie. De passkeys worden opnieuw geladen vanuit passkeys.json, wat laat zien dat de opslag bij uw applicatie hoort en niet bij het component.

Documentatie

Waar u het kunt krijgen

TsgcWSAPIServer_WebAuthn zit in de Enterprise- en All-Access-edities van sgcWebSockets, voor Delphi en C++ Builder, en hetzelfde component maakt deel uit van sgcWebSockets .NET. Als u alleen authenticatie nodig hebt, heeft het sgcAuth-pakket het samen met de andere login-componenten. U vindt het op het SGC Auth-palet, en er verandert niets in een bestaande applicatie totdat u het op een formulier plaatst.

Lees verder

Bekijk het

Er is een korte video, “Passkeys in Delphi: passwordless login with WebAuthn”, op het eSeGeCe-kanaal. Het toont de code in de IDE, wat de back-upvlaggen en de handtekeningteller vertellen, en de demo die draait op https://localhost: een gebruiker registreert twee passkeys en meldt zich vervolgens aan zonder een gebruikersnaam te typen.

Vragen, feedback of hulp bij het toevoegen van passkeys aan uw aanmeldpagina? Neem contact op. U krijgt antwoord van de mensen die de code hebben geschreven.