Implementa le passkey con la compilazione automatica dei moduli in un'app web

1. Prima di iniziare

L'utilizzo delle passkey al posto delle password è un ottimo modo per i siti web di rendere gli account utente più sicuri, semplici e facili da usare. Con una passkey, un utente può accedere a un sito web o a un'app utilizzando la funzionalità di blocco schermo del dispositivo, ad esempio un'impronta, il volto o il PIN del dispositivo. Prima che un utente possa accedere con una passkey, questa deve essere creata, associata a un account utente e la relativa chiave pubblica deve essere memorizzata su un server.

In questo codelab, trasformerai un accesso di base basato su moduli con nome utente e password in uno che supporta le passkey e include quanto segue:

  • Un pulsante che crea una passkey dopo l'accesso dell'utente.
  • Un'interfaccia utente che mostra un elenco di passkey registrate.
  • Il modulo di accesso esistente che consente agli utenti di accedere con una passkey registrata tramite la compilazione automatica dei moduli.

Prerequisiti

Obiettivi didattici

  • Come creare una passkey.
  • Come autenticare gli utenti con una passkey.
  • Come consentire a un modulo di suggerire una passkey come opzione di accesso.

Che cosa ti serve

Una delle seguenti combinazioni di dispositivi:

  • Google Chrome con un dispositivo Android con Android 9 o versioni successive, preferibilmente con un sensore biometrico.
  • Chrome con un dispositivo Windows con Windows 10 o versioni successive.
  • Safari 16 o versioni successive con un iPhone con iOS 16 o versioni successive o un iPad con iPadOS 16 o versioni successive.
  • Safari 16 o versioni successive o Chrome con un dispositivo desktop Apple con macOS Ventura o versioni successive.

2. Configurazione

In questo codelab utilizzi un servizio chiamato Glitch, che ti consente di modificare il codice lato client e server con JavaScript e di eseguirne il deployment solo dal browser.

Apri il progetto

  1. Apri il progetto in Glitch.
  2. Fai clic su Remixa per creare una copia del progetto Glitch.
  3. Nel menu di navigazione nella parte inferiore di Glitch, fai clic su Anteprima > Anteprima in una nuova finestra. Nel browser si apre un'altra scheda.

Il pulsante Anteprima in una nuova finestra nel menu di navigazione nella parte inferiore di Glitch

Esaminare lo stato iniziale del sito web

  1. Nella scheda di anteprima, inserisci un nome utente casuale e fai clic su Avanti.
  2. Inserisci una password casuale e poi fai clic su Accedi. La password viene ignorata, ma l'autenticazione viene comunque eseguita e viene visualizzata la home page.
  3. Se vuoi cambiare il tuo nome visualizzato, fallo. Questo è tutto ciò che puoi fare nello stato iniziale.
  4. Fai clic su Esci.

In questo stato, gli utenti devono inserire una password ogni volta che accedono. Aggiungi il supporto delle passkey a questo modulo in modo che gli utenti possano accedere con la funzionalità di blocco schermo del dispositivo. Puoi provare lo stato finale all'indirizzo https://passkeys-codelab.glitch.me/.

Per saperne di più sul funzionamento delle passkey, consulta Come funzionano le passkey?.

3. Aggiungere la possibilità di creare una passkey

Per consentire agli utenti di autenticarsi con una passkey, devi dare loro la possibilità di creare e registrare una passkey e memorizzare la relativa chiave pubblica sul server.

Al momento della creazione della passkey, viene visualizzata una finestra di dialogo di verifica dell'utente.

Vuoi consentire la creazione di una passkey dopo che l'utente ha eseguito l'accesso con una password e aggiungere un'interfaccia utente che consenta agli utenti di creare una passkey e visualizzare un elenco di tutte le passkey registrate nella pagina /home. Nella sezione successiva, creerai una funzione che crea e registra una passkey.

Crea la funzione registerCredential()

  1. In Glitch, vai al file public/client.js e scorri fino alla fine.
  2. Dopo il commento pertinente, aggiungi la seguente funzione registerCredential():

public/client. js

// TODO: Add an ability to create a passkey: Create the registerCredential() function. export async function registerCredential() {    // TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.    // TODO: Add an ability to create a passkey: Create a credential.    // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.  }; 

Questa funzione crea e registra una passkey sul server.

Ottenere la sfida e altre opzioni dall'endpoint del server

Prima di creare una passkey, devi richiedere al server i parametri da passare in WebAuthn, inclusa una sfida. WebAuthn è un'API del browser che consente a un utente di creare una passkey e di autenticarlo con la passkey. Fortunatamente, in questo codelab hai già un endpoint server che risponde con questi parametri.

  • Per ottenere la sfida e altre opzioni dall'endpoint del server, aggiungi il seguente codice al corpo della funzione registerCredential() dopo il commento pertinente:

public/client.js

// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint. const options = await _fetch('/auth/registerRequest'); 

Lo snippet di codice seguente include opzioni di esempio che ricevi dal server:

{   challenge: *****,   rp: {     id: "example.com",   },   user: {     id: *****,     name: "john78",     displayName: "John",   },     pubKeyCredParams: [{     alg: -7, type: "public-key"   },{     alg: -257, type: "public-key"   }],   excludeCredentials: [{     id: *****,     type: 'public-key',     transports: ['internal', 'hybrid'],   }],   authenticatorSelection: {     authenticatorAttachment: "platform",     requireResidentKey: true,   } } 

Il protocollo tra un server e un client non fa parte della specifica WebAuthn. Tuttavia, il server di questo codelab è progettato per restituire un JSON il più simile possibile al dizionario PublicKeyCredentialCreationOptions passato all'API WebAuthn navigator.credentials.create().

La seguente tabella non è esaustiva, ma contiene i parametri importanti nel dizionario PublicKeyCredentialCreationOptions:

Parametri

Descrizioni

challenge

Una richiesta generata dal server in un oggetto ArrayBuffer per questa registrazione. Questo campo è obbligatorio, ma non viene utilizzato durante la registrazione, a meno che non venga eseguita l'attestazione, un argomento avanzato che non viene trattato in questo codelab.

user.id

L'ID univoco di un utente. Questo valore deve essere un oggetto ArrayBuffer che non include informazioni sull'identità personale, come indirizzi email o nomi utente. Un valore casuale di 16 byte generato per account funziona bene.

user.name

Questo campo deve contenere un identificatore univoco per l'account riconoscibile dall'utente, ad esempio il suo indirizzo email o nome utente. Viene visualizzato nel selettore account. Se utilizzi un nome utente, utilizza lo stesso valore dell'autenticazione con password.

user.displayName

Questo campo è un nome facoltativo e intuitivo per l'account. Non deve essere univoco e potrebbe essere il nome scelto dall'utente. Se il tuo sito web non ha un valore adatto da includere qui, trasmetti una stringa vuota. A seconda del browser, questa opzione potrebbe essere visualizzata nel selettore account.

rp.id

Un ID relying party (RP) è un dominio. Un sito web può specificare il proprio dominio o un suffisso registrabile. Ad esempio, se l'origine di un RP è https://login.example.com:1337, l'ID RP può essere login.example.com o example.com. Se l'ID RP è specificato come example.com, l'utente può autenticarsi su login.example.com o su qualsiasi altro sottodominio di example.com.

pubKeyCredParams

Questo campo specifica gli algoritmi di chiave pubblica supportati dal RP. Ti consigliamo di impostarlo su [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. Specifica il supporto per ECDSA con P-256 e RSA PKCS#1 e il supporto di questi algoritmi offre una copertura completa.

excludeCredentials

Fornisce un elenco di ID credenziali già registrati per impedire la registrazione dello stesso dispositivo due volte. Se fornito, il membro transports deve contenere il risultato della chiamata alla funzione getTransports() durante la registrazione di ogni credenziale.

authenticatorSelection.authenticatorAttachment

Imposta un valore di "platform". Ciò indica che vuoi un autenticatore incorporato nel dispositivo della piattaforma, in modo che all'utente non venga chiesto di inserire un token di sicurezza USB o altro.

authenticatorSelection.requireResidentKey

Imposta un valore booleano true. Una credenziale rilevabile (chiave residente) può essere utilizzata senza che il server debba fornire l'ID della credenziale ed è quindi compatibile con il completamento automatico.

authenticatorSelection.userVerification

Imposta un valore "preferred" o omettilo perché è il valore predefinito. Indica se una verifica dell'utente che utilizza il blocco schermo del dispositivo è "required", "preferred" o "discouraged". Se il valore è impostato su "preferred", viene richiesta la verifica dell'utente quando il dispositivo è in grado di eseguirla.

Crea una credenziale

  1. Nel corpo della funzione registerCredential() dopo il commento pertinente, converti alcuni parametri codificati con Base64URL di nuovo in formato binario, in particolare le stringhe user.id e challenge e le istanze della stringa id incluse nell'array excludeCredentials:

public/client.js

// TODO: Add an ability to create a passkey: Create a credential. // Base64URL decode some values. options.user.id = base64url.decode(options.user.id); options.challenge = base64url.decode(options.challenge);  if (options.excludeCredentials) {   for (let cred of options.excludeCredentials) {     cred.id = base64url.decode(cred.id);   } } 
  1. Nella riga successiva, imposta authenticatorSelection.authenticatorAttachment su "platform" e authenticatorSelection.requireResidentKey su true. In questo modo, è consentito solo l'utilizzo di un autenticatore della piattaforma (il dispositivo stesso) con una funzionalità di credenziali rilevabili.

public/client.js

// Use platform authenticator and discoverable credential. options.authenticatorSelection = {   authenticatorAttachment: 'platform',   requireResidentKey: true } 
  1. Nella riga successiva, chiama il metodo navigator.credentials.create() per creare una credenziale.

public/client.js

// Invoke the WebAuthn create() method. const cred = await navigator.credentials.create({   publicKey: options, }); 

Con questa chiamata, il browser tenta di verificare l'identità dell'utente con il blocco schermo del dispositivo.

Registra la credenziale nell'endpoint del server

Dopo che l'utente ha verificato la propria identità, viene creata e memorizzata una passkey. Il sito web riceve un oggetto credenziale che contiene una chiave pubblica che puoi inviare al server per registrare la passkey.

Il seguente snippet di codice contiene un oggetto credenziali di esempio:

{   "id": *****,   "rawId": *****,   "type": "public-key",   "response": {     "clientDataJSON": *****,     "attestationObject": *****,     "transports": ["internal", "hybrid"]   },   "authenticatorAttachment": "platform" } 

La seguente tabella non è esaustiva, ma contiene i parametri importanti nell'oggetto PublicKeyCredential:

Parametri

Descrizioni

id

Un ID con codifica Base64URL della passkey creata. Questo ID aiuta il browser a determinare se è presente una passkey corrispondente nel dispositivo durante l'autenticazione. Questo valore deve essere memorizzato nel database sul backend.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziali.

response.clientDataJSON

Un oggetto ArrayBuffer codifica i dati del cliente.

response.attestationObject

Un oggetto di attestazione codificato ArrayBuffer. Contiene informazioni importanti, come un ID RP, flag e una chiave pubblica.

response.transports

Un elenco dei protocolli di trasporto supportati dal dispositivo: "internal" significa che il dispositivo supporta una passkey. "hybrid" significa che supporta anche l'autenticazione su un altro dispositivo.

authenticatorAttachment

Restituisce "platform" quando questa credenziale viene creata su un dispositivo compatibile con le passkey.

Per inviare l'oggetto credenziale al server:

  1. Codifica i parametri binari della credenziale come Base64URL in modo che possa essere inviata al server come stringa:

public/client.js

// TODO: Add an ability to create a passkey: Register the credential to the server endpoint. const credential = {}; credential.id = cred.id; credential.rawId = cred.id; // Pass a Base64URL encoded ID string. credential.type = cred.type;  // The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3. if (cred.authenticatorAttachment) {   credential.authenticatorAttachment = cred.authenticatorAttachment; }  // Base64URL encode some values. const clientDataJSON = base64url.encode(cred.response.clientDataJSON); const attestationObject = base64url.encode(cred.response.attestationObject);  // Obtain transports. const transports = cred.response.getTransports ? cred.response.getTransports() : [];  credential.response = {   clientDataJSON,   attestationObject,   transports }; 
  1. Nella riga successiva, invia l'oggetto al server:

public/client.js

return await _fetch('/auth/registerResponse', credential); 

Quando esegui il programma, il server restituisce HTTP code 200, che indica che la credenziale è registrata.

Ora hai la funzione registerCredential() completa.

Esamina il codice della soluzione per questa sezione

public/client.js

// TODO: Add an ability to create a passkey: Create the registerCredential() function. export async function registerCredential() {    // TODO: Add an ability to create a passkey: Obtain the challenge and other options from server endpoint.   const options = await _fetch('/auth/registerRequest');      // TODO: Add an ability to create a passkey: Create a credential.   // Base64URL decode some values.    options.user.id = base64url.decode(options.user.id);   options.challenge = base64url.decode(options.challenge);    if (options.excludeCredentials) {     for (let cred of options.excludeCredentials) {       cred.id = base64url.decode(cred.id);     }   }    // Use platform authenticator and discoverable credential.   options.authenticatorSelection = {     authenticatorAttachment: 'platform',     requireResidentKey: true   }    // Invoke the WebAuthn create() method.   const cred = await navigator.credentials.create({     publicKey: options,   });    // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.   const credential = {};   credential.id = cred.id;   credential.rawId = cred.id; // Pass a Base64URL encoded ID string.   credential.type = cred.type;    // The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.   if (cred.authenticatorAttachment) {     credential.authenticatorAttachment = cred.authenticatorAttachment;   }    // Base64URL encode some values.   const clientDataJSON = base64url.encode(cred.response.clientDataJSON);   const attestationObject =     base64url.encode(cred.response.attestationObject);    // Obtain transports.   const transports = cred.response.getTransports ?    cred.response.getTransports() : [];    credential.response = {     clientDataJSON,     attestationObject,     transports   };    return await _fetch('/auth/registerResponse', credential); }; 

4. Creare un'interfaccia utente per registrare e gestire le credenziali passkey

Ora che la funzione registerCredential() è disponibile, ti serve un pulsante per richiamarla. Inoltre, devi mostrare un elenco di passkey registrate.

Passkey registrate elencate nella pagina /home

Aggiungere HTML segnaposto

  1. In Glitch, vai al file views/home.html.
  2. Dopo il commento pertinente, aggiungi un segnaposto dell'interfaccia utente che mostri un pulsante per registrare una passkey e un elenco di passkey:

views/home.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. --> <section>   <h3 class="mdc-typography mdc-typography--headline6"> Your registered    passkeys:</h3>   <div id="list"></div> </section> <p id="message" class="instructions"></p> <mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button> 

L'elemento div#list è il segnaposto per l'elenco.

Controllare il supporto delle passkey

Per mostrare l'opzione per creare una passkey solo agli utenti con dispositivi che le supportano, devi prima verificare se WebAuthn è disponibile. In questo caso, devi rimuovere la classe hidden per visualizzare il pulsante Crea una passkey.

Per verificare se un ambiente supporta le passkey:

  1. Alla fine del file views/home.html dopo il commento pertinente, scrivi una condizione che viene eseguita se window.PublicKeyCredential, PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable e PublicKeyCredential.isConditionalMediationAvailable sono true.

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support. const createPasskey = $('#create-passkey'); // Feature detections if (window.PublicKeyCredential &&     PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&     PublicKeyCredential.isConditionalMediationAvailable) { 
  1. Nel corpo della condizione, controlla se il dispositivo può creare una passkey e se può essere suggerita in un modulo di compilazione automatica.

views/home.html

try {   const results = await Promise.all([      // Is platform authenticator available in this browser?     PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),      // Is conditional UI available in this browser?     PublicKeyCredential.isConditionalMediationAvailable()   ]); 
  1. Se tutte le condizioni sono soddisfatte, mostra il pulsante per creare una passkey. In caso contrario, mostra un messaggio di avviso.

views/home.html

    if (results.every(r => r === true)) {        // If conditional UI is available, reveal the Create a passkey button.       createPasskey.classList.remove('hidden');     } else {        // If conditional UI isn't available, show a message.       $('#message').innerText = 'This device does not support passkeys.';     }   } catch (e) {     console.error(e);   } } else {    // If WebAuthn isn't available, show a message.   $('#message').innerText = 'This device does not support passkeys.'; } 

Visualizzare le passkey registrate in un elenco

  1. Definisci una funzione renderCredentials() che recupera le passkey registrate dal server e le visualizza in un elenco. Fortunatamente, hai già l'endpoint del server /auth/getKeys per recuperare le passkey registrate per l'utente che ha eseguito l'accesso.

views/home.html

// TODO: Add an ability to create a passkey: Render registered passkeys in a list. async function renderCredentials() {   const res = await _fetch('/auth/getKeys');   const list = $('#list');   const creds = html`${res.length > 0 ? html`     <mwc-list>       ${res.map(cred => html`         <mwc-list-item>           <div class="list-item">             <div class="entity-name">               <span>${cred.name || 'Unnamed' }</span>           </div>           <div class="buttons">             <mwc-icon-button data-cred-id="${cred.id}"               data-name="${cred.name || 'Unnamed' }" @click="${rename}"               icon="edit"></mwc-icon-button>             <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}"              icon="delete"></mwc-icon-button>           </div>          </div>       </mwc-list-item>`)}   </mwc-list>` : html`   <mwc-list>     <mwc-list-item>No credentials found.</mwc-list-item>   </mwc-list>`}`;   render(creds, list); }; 
  1. Nella riga successiva, richiama la funzione renderCredentials() per visualizzare le passkey registrate non appena l'utente arriva alla pagina /home come inizializzazione.

views/home.html

renderCredentials(); 

Creare e registrare una passkey

Per creare e registrare una passkey, devi chiamare la funzione registerCredential() che hai implementato in precedenza.

Per attivare la funzione registerCredential() quando fai clic sul pulsante Crea una passkey, segui questi passaggi:

  1. Nel file dopo il codice HTML segnaposto, trova la seguente istruzione import:

views/home.html

import {    $,    _fetch,    loading,    updateCredential,    unregisterCredential,  } from '/client.js'; 
  1. Alla fine del corpo dell'istruzione import, aggiungi la funzione registerCredential().

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey. import {   $,   _fetch,   loading,   updateCredential,   unregisterCredential,   registerCredential } from '/client.js'; 
  1. Alla fine del file, dopo il commento pertinente, definisci una funzione register() che richiama la funzione registerCredential() e una UI di caricamento e chiama renderCredentials() dopo una registrazione. Ciò chiarisce che il browser crea una passkey e mostra un messaggio di errore quando si verifica un problema.

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey. async function register() {   try {      // Start the loading UI.     loading.start();      // Start creating a passkey.     await registerCredential();      // Stop the loading UI.     loading.stop();      // Render the updated passkey list.     renderCredentials(); 
  1. Nel corpo della funzione register(), intercetta le eccezioni. Il metodo navigator.credentials.create() genera un errore InvalidStateError quando sul dispositivo esiste già una passkey. Viene esaminato con l'array excludeCredentials. In questo caso, mostri all'utente un messaggio pertinente. Inoltre, genera un errore NotAllowedError quando l'utente annulla la finestra di dialogo di autenticazione. In questo caso, lo ignori silenziosamente.

views/home.html

  } catch (e) {      // Stop the loading UI.     loading.stop();      // An InvalidStateError indicates that a passkey already exists on the device.     if (e.name === 'InvalidStateError') {       alert('A passkey already exists for this device.');      // A NotAllowedError indicates that the user canceled the operation.     } else if (e.name === 'NotAllowedError') {       Return;      // Show other errors in an alert.     } else {       alert(e.message);       console.error(e);     }   } }; 
  1. Nella riga dopo la funzione register(), associa la funzione register() a un evento click per il pulsante Crea una passkey.

views/home.html

createPasskey.addEventListener('click', register); 

Esamina il codice della soluzione per questa sezione

views/home.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. --> <section>   <h3 class="mdc-typography mdc-typography--headline6"> Your registered     passkeys:</h3>   <div id="list"></div> </section> <p id="message" class="instructions"></p> <mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button> 

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey. import {    $,    _fetch,    loading,    updateCredential,    unregisterCredential,    registerCredential  } from '/client.js'; 

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support. const createPasskey = $('#create-passkey');  // Feature detections if (window.PublicKeyCredential &&     PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&     PublicKeyCredential.isConditionalMediationAvailable) {   try {     const results = await Promise.all([        // Is platform authenticator available in this browser?       PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),        // Is conditional UI available in this browser?       PublicKeyCredential.isConditionalMediationAvailable()     ]);     if (results.every(r => r === true)) {        // If conditional UI is available, reveal the Create a passkey button.       createPasskey.classList.remove('hidden');     } else {        // If conditional UI isn't available, show a message.       $('#message').innerText = 'This device does not support passkeys.';     }   } catch (e) {     console.error(e);   } } else {    // If WebAuthn isn't available, show a message.   $('#message').innerText = 'This device does not support passkeys.'; }  // TODO: Add an ability to create a passkey: Render registered passkeys in a list. async function renderCredentials() {   const res = await _fetch('/auth/getKeys');   const list = $('#list');   const creds = html`${res.length > 0 ? html`   <mwc-list>     ${res.map(cred => html`       <mwc-list-item>         <div class="list-item">           <div class="entity-name">             <span>${cred.name || 'Unnamed' }</span>           </div>           <div class="buttons">             <mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>             <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>           </div>         </div>       </mwc-list-item>`)}   </mwc-list>` : html`   <mwc-list>     <mwc-list-item>No credentials found.</mwc-list-item>   </mwc-list>`}`;   render(creds, list); };  renderCredentials();  // TODO: Add an ability to create a passkey: Create and register a passkey. async function register() {   try {      // Start the loading UI.     loading.start();      // Start creating a passkey.     await registerCredential();      // Stop the loading UI.     loading.stop();      // Render the updated passkey list.     renderCredentials();   } catch (e) {      // Stop the loading UI.     loading.stop();      // An InvalidStateError indicates that a passkey already exists on the device.     if (e.name === 'InvalidStateError') {       alert('A passkey already exists for this device.');      // A NotAllowedError indicates that the user canceled the operation.     } else if (e.name === 'NotAllowedError') {       Return;      // Show other errors in an alert.     } else {       alert(e.message);       console.error(e);     }   } };  createPasskey.addEventListener('click', register); 

Prova

Se hai seguito tutti i passaggi finora, hai implementato la possibilità di creare, registrare e visualizzare le passkey sul sito web.

Per provarlo, segui questi passaggi:

  1. Nella scheda di anteprima, accedi con un nome utente e una password casuali.
  2. Fai clic su Crea una passkey.
  3. Verifica la tua identità con il blocco schermo del dispositivo.
  4. Verifica che una passkey sia registrata e visualizzata nella sezione Le tue passkey registrate della pagina web.

Passkey registrate elencate nella pagina /home.

Rinominare e rimuovere le passkey registrate

Dovresti essere in grado di rinominare o eliminare le passkey registrate nell'elenco. Puoi controllare come funziona nel codice, poiché sono inclusi nel codelab.

In Chrome, puoi rimuovere le passkey registrate da chrome://settings/passkeys sul computer o dal Gestore delle password nelle impostazioni su Android.

Per informazioni su come rinominare e rimuovere le passkey registrate su altre piattaforme, consulta le rispettive pagine di assistenza.

5. Aggiungere la possibilità di autenticarsi con una passkey

Ora gli utenti possono creare e registrare una passkey e sono pronti a utilizzarla come metodo di autenticazione sicuro per il tuo sito web. Ora devi aggiungere una funzionalità di autenticazione con passkey al tuo sito web.

Crea la funzione authenticate()

  • Nel file public/client.js, dopo il commento pertinente, crea una funzione chiamata authenticate() che verifica l'utente localmente e poi sul server:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function. export async function authenticate() {    // TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.    // TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.    // TODO: Add an ability to authenticate with a passkey: Verify the credential.  }; 

Ottenere la sfida e altre opzioni dall'endpoint del server

Prima di chiedere all'utente di autenticarsi, devi richiedere al server i parametri da passare in WebAuthn, inclusa una sfida.

  • Nel corpo della funzione authenticate() dopo il commento pertinente, chiama la funzione _fetch() per inviare una richiesta POST al server:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint. const options = await _fetch('/auth/signinRequest'); 

Il server di questo codelab è progettato per restituire un JSON il più simile possibile al dizionario PublicKeyCredentialRequestOptions passato all'API WebAuthn navigator.credentials.get(). Il seguente snippet di codice include opzioni di esempio che dovresti ricevere:

{   "challenge": *****,   "rpId": "passkeys-codelab.glitch.me",   "allowCredentials": [] } 

La seguente tabella non è esaustiva, ma contiene i parametri importanti nel dizionario PublicKeyCredentialRequestOptions:

Parametri

Descrizioni

challenge

Una verifica generata dal server in un oggetto ArrayBuffer. Questo è necessario per impedire gli attacchi di replay. Non accettare mai due volte la stessa sfida in una risposta. Consideralo un token CSRF.

rpId

Un ID RP è un dominio. Un sito web può specificare il proprio dominio o un suffisso registrabile. Questo valore deve corrispondere al parametro rp.id utilizzato al momento della creazione della passkey.

allowCredentials

Questa proprietà viene utilizzata per trovare autenticatori idonei per questa autenticazione. Passa un array vuoto o lascialo non specificato per consentire al browser di mostrare un selettore dell'account.

userVerification

Imposta un valore "preferred" o omettilo perché è il valore predefinito. Indica se la verifica dell'utente tramite il blocco schermo del dispositivo è "required", "preferred" o "discouraged". Se il valore è impostato su "preferred", viene richiesta la verifica dell'utente quando il dispositivo è in grado di eseguirla.

Verifica localmente l'utente e ottieni una credenziale

  1. Nel corpo della funzione authenticate(), dopo il commento pertinente, converti di nuovo il parametro challenge in formato binario:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential. // Base64URL decode the challenge. options.challenge = base64url.decode(options.challenge); 
  1. Passa un array vuoto al parametro allowCredentials per aprire un selettore account quando un utente esegue l'autenticazione:

public/client.js

// An empty allowCredentials array invokes an account selector by discoverable credentials. options.allowCredentials = []; 

Il selettore dell'account utilizza le informazioni dell'utente memorizzate con la passkey.

  1. Chiama il metodo navigator.credentials.get() insieme a un'opzione mediation: 'conditional':

public/client.js

// Invoke the WebAuthn get() method. const cred = await navigator.credentials.get({   publicKey: options,    // Request a conditional UI.   mediation: 'conditional' }); 

Questa opzione indica al browser di suggerire le passkey in modo condizionale nell'ambito del completamento automatico dei moduli.

Verifica della credenziale

Dopo che l'utente ha verificato la propria identità localmente, dovresti ricevere un oggetto credenziale contenente una firma che puoi verificare sul server.

Il seguente snippet di codice include un oggetto PublicKeyCredential di esempio:

{   "id": *****,   "rawId": *****,   "type": "public-key",   "response": {     "clientDataJSON": *****,     "authenticatorData": *****,     "signature": *****,     "userHandle": *****   },   authenticatorAttachment: "platform" } 

La seguente tabella non è esaustiva, ma contiene i parametri importanti nell'oggetto PublicKeyCredential:

Parametri

Descrizioni

id

L'ID con codifica Base64URL della credenziale passkey autenticata.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziali.

response.clientDataJSON

Un oggetto ArrayBuffer di dati del cliente. Questo campo contiene informazioni, come la verifica e l'origine che il server RP deve verificare.

response.authenticatorData

Un oggetto ArrayBuffer di dati dell'autenticatore. Questo campo contiene informazioni come l'ID RP.

response.signature

Un oggetto ArrayBuffer della firma. Questo valore è il fulcro della credenziale e deve essere verificato sul server.

response.userHandle

Un oggetto ArrayBuffer che contiene l'ID utente impostato al momento della creazione. Questo valore può essere utilizzato al posto dell'ID credenziale se il server deve scegliere i valori ID da utilizzare o se il backend vuole evitare la creazione di un indice sugli ID credenziali.

authenticatorAttachment

Restituisce una stringa "platform" quando queste credenziali provengono dal dispositivo locale. In caso contrario, restituisce una stringa "cross-platform", in particolare quando l'utente utilizza un telefono per accedere. Se l'utente deve utilizzare uno smartphone per accedere, invitalo a creare una passkey sul dispositivo locale.

Per inviare l'oggetto credenziale al server:

  1. Nel corpo della funzione authenticate() dopo il commento pertinente, codifica i parametri binari della credenziale in modo che possa essere inviata al server come stringa:

public/client.js

// TODO: Add an ability to authenticate with a passkey: Verify the credential. const credential = {}; credential.id = cred.id; credential.rawId = cred.id; // Pass a Base64URL encoded ID string. credential.type = cred.type;  // Base64URL encode some values. const clientDataJSON = base64url.encode(cred.response.clientDataJSON); const authenticatorData = base64url.encode(cred.response.authenticatorData); const signature = base64url.encode(cred.response.signature); const userHandle = base64url.encode(cred.response.userHandle);  credential.response = {   clientDataJSON,   authenticatorData,   signature,   userHandle, }; 
  1. Invia l'oggetto al server:

public/client.js

return await _fetch(`/auth/signinResponse`, credential); 

Quando esegui il programma, il server restituisce HTTP code 200, che indica che la credenziale è verificata.

Ora hai la funzione completa authentication().

Esamina il codice della soluzione per questa sezione

public/client.js

// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function. export async function authenticate() {    // TODO: Add an ability to authenticate with a passkey: Obtain the    challenge and other options from the server endpoint.   const options = await _fetch('/auth/signinRequest');    // TODO: Add an ability to authenticate with a passkey: Locally verify    the user and get a credential.   // Base64URL decode the challenge.   options.challenge = base64url.decode(options.challenge);    // The empty allowCredentials array invokes an account selector    by discoverable credentials.   options.allowCredentials = [];    // Invoke the WebAuthn get() function.   const cred = await navigator.credentials.get({     publicKey: options,      // Request a conditional UI.     mediation: 'conditional'   });    // TODO: Add an ability to authenticate with a passkey: Verify the credential.   const credential = {};   credential.id = cred.id;   credential.rawId = cred.id; // Pass a Base64URL encoded ID string.   credential.type = cred.type;    // Base64URL encode some values.   const clientDataJSON = base64url.encode(cred.response.clientDataJSON);   const authenticatorData =    base64url.encode(cred.response.authenticatorData);   const signature = base64url.encode(cred.response.signature);   const userHandle = base64url.encode(cred.response.userHandle);    credential.response = {     clientDataJSON,     authenticatorData,     signature,     userHandle,   };    return await _fetch(`/auth/signinResponse`, credential); }; 

6. Aggiungere passkey al riempimento automatico del browser

Quando l'utente torna, vuoi che acceda nel modo più semplice e sicuro possibile. Se aggiungi un pulsante Accedi con una passkey alla pagina di accesso, l'utente può premere il pulsante, selezionare una passkey nel selettore dell'account del browser e utilizzare il blocco schermo per verificare l'identità.

Tuttavia, la transizione da una password a una passkey non avviene per tutti gli utenti contemporaneamente. Ciò significa che non puoi eliminare le password finché tutti gli utenti non passano alle passkey, quindi devi lasciare il modulo di accesso basato su password fino ad allora. Tuttavia, se lasci un modulo per la password e un pulsante per la passkey, gli utenti dovranno fare una scelta inutile tra quale utilizzare per accedere. L'ideale sarebbe una procedura di accesso semplice.

È qui che entra in gioco un'interfaccia utente condizionale. Un'interfaccia utente condizionale è una funzionalità WebAuthn che ti consente di creare un campo di input del modulo per suggerire una passkey come parte degli elementi di compilazione automatica, oltre alle password. Se un utente tocca una passkey nei suggerimenti di compilazione automatica, gli viene chiesto di utilizzare il blocco schermo del dispositivo per verificare localmente la sua identità. Si tratta di un'esperienza utente fluida perché l'azione dell'utente è quasi identica a quella di un accesso basato su password.

Una passkey suggerita nell&#39;ambito della compilazione automatica dei moduli.

Attivare un'interfaccia condizionale

Per attivare un'interfaccia utente condizionale, devi solo aggiungere un token webauthn all'attributo autocomplete di un campo di input. Con il set di token, puoi chiamare il metodo navigator.credentials.get() con la stringa mediation: 'conditional' per attivare in modo condizionale la UI di blocco schermo.

  • Per attivare un'interfaccia utente condizionale, sostituisci i campi di input del nome utente esistenti con il seguente codice HTML dopo il commento pertinente nel file view/index.html:

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. --> <input   type="text"   id="username"   class="mdc-text-field__input"   aria-labelledby="username-label"   name="username"   autocomplete="username webauthn"   autofocus /> 

Rileva le funzionalità, richiama WebAuthn e abilita un'interfaccia utente condizionale

  1. Nel file view/index.html dopo il commento pertinente, sostituisci l'istruzione import esistente con il seguente codice:

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI. import {   $,   _fetch,   loading,   authenticate  } from "/client.js"; 

Questo codice importa la funzione authenticate() che hai implementato in precedenza.

  1. Verifica che l'oggetto window.PulicKeyCredential sia disponibile e che il metodo PublicKeyCredential.isConditionalMediationAvailable() restituisca un valore true, quindi chiama la funzione authenticate():

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI. if (   window.PublicKeyCredential &&   PublicKeyCredential.isConditionalMediationAvailable ) {   try {      // Is conditional UI available in this browser?     const cma =       await PublicKeyCredential.isConditionalMediationAvailable();     if (cma) {        // If conditional UI is available, invoke the authenticate() function.       const user = await authenticate();       if (user) {          // Proceed only when authentication succeeds.         $("#username").value = user.username;         loading.start();         location.href = "/home";       } else {         throw new Error("User not found.");       }     }   } catch (e) {     loading.stop();      // A NotAllowedError indicates that the user canceled the operation.     if (e.name !== "NotAllowedError") {       console.error(e);       alert(e.message);     }   } } 

Esamina il codice della soluzione per questa sezione

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. --> <input   type="text"   id="username"   class="mdc-text-field__input"   aria-labelledby="username-label"   name="username"   autocomplete="username webauthn"   autofocus  /> 

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI. import {    $,    _fetch,    loading,    authenticate  } from '/client.js'; 

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.         // Is WebAuthn avaiable in this browser? if (window.PublicKeyCredential &&     PublicKeyCredential.isConditionalMediationAvailable) {   try {      // Is a conditional UI available in this browser?     const cma= await PublicKeyCredential.isConditionalMediationAvailable();     if (cma) {        // If a conditional UI is available, invoke the authenticate() function.       const user = await authenticate();       if (user) {          // Proceed only when authentication succeeds.         $('#username').value = user.username;         loading.start();         location.href = '/home';       } else {         throw new Error('User not found.');       }     }   } catch (e) {     loading.stop();      // A NotAllowedError indicates that the user canceled the operation.     if (e.name !== 'NotAllowedError') {       console.error(e);       alert(e.message);     }   } } 

Prova

Hai implementato la creazione, la registrazione, la visualizzazione e l'autenticazione delle passkey sul tuo sito web.

Per provarlo, segui questi passaggi:

  1. Vai alla scheda Anteprima.
  2. Se necessario, esci.
  3. Fai clic sulla casella di testo del nome utente. Viene visualizzata una finestra di dialogo.
  4. Seleziona l'account con cui vuoi accedere.
  5. Verifica la tua identità con il blocco schermo del dispositivo. Viene visualizzata la pagina /home e hai eseguito l'accesso.

Una finestra di dialogo che ti chiede di verificare la tua identità con la password o la passkey salvata.

7. Complimenti!

Hai completato questo codelab. Se hai domande, pubblicale nella mailing list FIDO-DEV o su Stack Overflow con il tag passkey.

Scopri di più