spinny:~/writing $ less passkeys-webauthn-passwordless-authentication.md
12Wachtwoorden zijn een van die dingen die we hebben genormaliseerd, alleen maar omdat we er al jaren mee leven. Gebruikers vergeten ze, hergebruiken ze, schrijven ze waar ze niet mogen. Teams moeten resets, beleid, hashes, lekken, phishing en ondersteuning beheren.34De passkey maken de authenticatie niet perfect, maar ze nemen een groot probleem weg: de server hoeft niet langer een geheim te bewaren dat met de gebruiker wordt gedeeld.56## Wat er echt gebeurt78Een passkey is een referentie gebaseerd op WebAuthn. Wanneer de gebruiker het aanmaakt, genereert het apparaat een sleutelpaar:910- een privésleutel, die op het apparaat of in de wachtwoordbeheerder blijft staan;11- een publieke sleutel, die de server kan opslaan.1213Bij het inloggen vraagt de server niet "vertel mij het wachtwoord". Stuur een willekeurige uitdaging. Het apparaat ondertekent het met de privésleutel. De server verifieert de handtekening met de publieke sleutel.1415Dat is het leuke: als de database wordt gestolen, zijn er geen wachtwoorden meer om te kraken. En als een gebruiker op een nepdomein terechtkomt, is de passkey niet geldig voor dat domein. Het is niet alleen gemak, het is concrete bescherming tegen phishing.1617## De browser fungeert als een brug1819In de browser zijn de twee belangrijkste API:2021- `navigator.credentials.create()` om een passkey te maken;22- `navigator.credentials.get()` om het te gebruiken tijdens het inloggen.2324Maar de belangrijke logica zit op de server. De server moet de uitdaging genereren, deze tijdelijk opslaan, het antwoord verifiëren, de bron en Relying Party ID controleren en vervolgens de sessie creëren.2526Het klantgedeelte zou bijna saai moeten zijn:2728```typescript29const options = await fetch('/api/passkeys/login/options').then((r) => r.json());3031const credential = await navigator.credentials.get({32 publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options),33});3435await fetch('/api/passkeys/login/verify', {36 method: 'POST',37 headers: { 'Content-Type': 'application/json' },38 body: JSON.stringify(credential?.toJSON()),39});40```4142Als u merkt dat u WebAuthn-codering handmatig implementeert, stop dan. Gebruik een robuuste server-side bibliotheek. De fouten hier zijn geen "schattige bugs", het zijn authenticatiegaten.4344## Wat u in de database moet opslaan4546Het is niet nodig om de halve wereld te redden. Meestal genoeg:4748- ID van de identificatie;49- publieke sleutel;50- aangesloten gebruiker;51- eventuele verificatieteller of metadata;52- transporten, indien nuttig voor de UX;53- naam gekozen door de gebruiker;54- aanmaakdatum en laatste gebruik.5556Een minimale tabel zou er als volgt uit kunnen zien:5758```sql59create table passkey_credentials (60 id uuid primary key default gen_random_uuid(),61 user_id uuid not null references users(id),62 credential_id text not null unique,63 public_key text not null,64 name text,65 created_at timestamptz not null default now(),66 last_used_at timestamptz67);68```6970Vervolgens zou ik auditlogboeken en meldingen toevoegen: als iemand een nieuwe passkey op mijn account aanmaakt, wil ik daarvan op de hoogte zijn.7172## UX is belangrijker dan demo7374De demo van een passkey is altijd mooi: je klikt, Face ID, je doet mee. Het eigenlijke product is ingewikkelder.7576Iemand verandert van telefoon. Iemand gebruikt een vergrendelde bedrijfscomputer. Sommige mensen begrijpen niet waarom de browser een passkey voorstelt. Iemand verliest de toegang tot zijn apparaat.7778Daarom zou ik niet beginnen met "vanaf vandaag geen wachtwoorden meer voor iedereen". Ik zou zo beginnen:79801. passkey optioneel voor interne gebruikers;812. suggestie om er een aan te maken na een succesvolle login;823. accountpagina om passkey te hernoemen en te verwijderen;834. duidelijke terugval;845. geleidelijke uitrol in de hoofdlogin.8586De tekst in de interface moet eenvoudig zijn. 'Gebruik het vergrendelingsscherm van uw apparaat' is beter dan 'authenticeren met een inwoner FIDO2-referentie'.8788## Fouten die ik zou vermijden8990Genereer geen uitdagingen bij de cliënt. De uitdaging wordt op de server aangemaakt en hoeft slechts één keer te worden geverifieerd.9192Vertrouw niet alleen op de inlog-ID. U moet de handtekening, uitdaging, herkomst en Relying Party ID verifiëren.9394Verwijder geen fallbacks voordat u een goede herstelstroom heeft. Wachtwoordloos hoeft niet te worden "als je je telefoon kwijtraakt, ben je voor altijd weg".9596Beschouw de passkey niet als een puur frontend-knop. Het verbergen van een knop is geen beveiliging: de echte verificatie vindt plaats aan de serverzijde.9798## Passkey-eerst of passkey-vriendelijk?99100Bij een nieuw product kun je passkey eerst denken. Voor een bestaande app geef ik de voorkeur aan passkey-vriendelijk: voeg passkey toe als aanbevolen methode, meet succes en problemen en verlaag vervolgens rustig het wachtwoordgewicht.101102De ideale migratie wordt niet gevoeld. De gebruiker ontdekt dat inloggen makkelijker gaat, niet dat het bedrijf zijn authenticatieprotocol heeft gewijzigd.103104## Conclusie105106De passkey zijn interessant omdat ze tegelijkertijd de beveiliging en UX verbeteren, wat zeldzaam is. Ze zijn geen toverstaf: herstel, compatibiliteit, ondersteuning en uitrol moeten nog goed worden ontworpen.107108Maar de fundamentele verandering is sterk. Vraag gebruikers niet langer om geheimen te verzinnen en te beschermen. U laat het apparaat een cryptografisch bewijs ondertekenen dat aan uw domein is gekoppeld. Minder menselijk geheugen, minder phishing, minder wachtwoordresets. Ik zou zeggen dat ze de moeite waard zijn om serieus te nemen.109110## Bronnen111112- [MDN: Passkeys](https://developer.mozilla.org/en-US/docs/Web/Security/Authentication/Passkeys)113- [MDN: Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)114- [W3C: Web Authentication](https://www.w3.org/TR/webauthn-3/)115- [passkeys.dev](https://passkeys.dev/)116
:Passkey en WebAuthn: inloggen zonder wachtwoord, zonder magielines 1-116 (END) — press q to close