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
- Conoscenza di base di JavaScript
- Conoscenza di base delle passkey
- Conoscenza di base dell'API Web Authentication (WebAuthn)
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
- Apri il progetto in Glitch.
- Fai clic su Remixa per creare una copia del progetto Glitch.
- 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.
Esaminare lo stato iniziale del sito web
- Nella scheda di anteprima, inserisci un nome utente casuale e fai clic su Avanti.
- 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.
- Se vuoi cambiare il tuo nome visualizzato, fallo. Questo è tutto ciò che puoi fare nello stato iniziale.
- 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.
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()
- In Glitch, vai al file
public/client.js
e scorri fino alla fine. - 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 |
Una richiesta generata dal server in un oggetto | |
L'ID univoco di un utente. Questo valore deve essere un oggetto | |
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. | |
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. | |
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 | |
Questo campo specifica gli algoritmi di chiave pubblica supportati dal RP. Ti consigliamo di impostarlo su | |
Fornisce un elenco di ID credenziali già registrati per impedire la registrazione dello stesso dispositivo due volte. Se fornito, il membro | |
Imposta un valore di | |
Imposta un valore booleano | |
Imposta un valore |
Crea una credenziale
- Nel corpo della funzione
registerCredential()
dopo il commento pertinente, converti alcuni parametri codificati con Base64URL di nuovo in formato binario, in particolare le stringheuser.id
echallenge
e le istanze della stringaid
incluse nell'arrayexcludeCredentials
:
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); } }
- Nella riga successiva, imposta
authenticatorSelection.authenticatorAttachment
su"platform"
eauthenticatorSelection.requireResidentKey
sutrue
. 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 }
- 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 |
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. | |
Una versione dell'oggetto | |
Un oggetto | |
Un oggetto di attestazione codificato | |
Un elenco dei protocolli di trasporto supportati dal dispositivo: | |
Restituisce |
Per inviare l'oggetto credenziale al server:
- 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 };
- 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.
Aggiungere HTML segnaposto
- In Glitch, vai al file
views/home.html
. - 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:
- Alla fine del file
views/home.html
dopo il commento pertinente, scrivi una condizione che viene eseguita sewindow.PublicKeyCredential
,PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable
ePublicKeyCredential.isConditionalMediationAvailable
sonotrue
.
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) {
- 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() ]);
- 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
- 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); };
- 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:
- Nel file dopo il codice HTML segnaposto, trova la seguente istruzione
import
:
views/home.html
import { $, _fetch, loading, updateCredential, unregisterCredential, } from '/client.js';
- Alla fine del corpo dell'istruzione
import
, aggiungi la funzioneregisterCredential()
.
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';
- Alla fine del file, dopo il commento pertinente, definisci una funzione
register()
che richiama la funzioneregisterCredential()
e una UI di caricamento e chiamarenderCredentials()
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();
- Nel corpo della funzione
register()
, intercetta le eccezioni. Il metodonavigator.credentials.create()
genera un erroreInvalidStateError
quando sul dispositivo esiste già una passkey. Viene esaminato con l'arrayexcludeCredentials
. In questo caso, mostri all'utente un messaggio pertinente. Inoltre, genera un erroreNotAllowedError
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); } } };
- Nella riga dopo la funzione
register()
, associa la funzioneregister()
a un eventoclick
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:
- Nella scheda di anteprima, accedi con un nome utente e una password casuali.
- Fai clic su Crea una passkey.
- Verifica la tua identità con il blocco schermo del dispositivo.
- Verifica che una passkey sia registrata e visualizzata nella sezione Le tue passkey registrate della pagina web.
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 chiamataauthenticate()
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 richiestaPOST
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 |
Una verifica generata dal server in un oggetto | |
Un ID RP è un dominio. Un sito web può specificare il proprio dominio o un suffisso registrabile. Questo valore deve corrispondere al parametro | |
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. | |
Imposta un valore |
Verifica localmente l'utente e ottieni una credenziale
- Nel corpo della funzione
authenticate()
, dopo il commento pertinente, converti di nuovo il parametrochallenge
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);
- 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.
- Chiama il metodo
navigator.credentials.get()
insieme a un'opzionemediation: '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 |
L'ID con codifica Base64URL della credenziale passkey autenticata. | |
Una versione dell'oggetto | |
Un oggetto | |
Un oggetto | |
Un oggetto | |
Un oggetto | |
Restituisce una stringa |
Per inviare l'oggetto credenziale al server:
- 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, };
- 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.
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
- Nel file
view/index.html
dopo il commento pertinente, sostituisci l'istruzioneimport
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.
- Verifica che l'oggetto
window.PulicKeyCredential
sia disponibile e che il metodoPublicKeyCredential.isConditionalMediationAvailable()
restituisca un valoretrue
, quindi chiama la funzioneauthenticate()
:
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:
- Vai alla scheda Anteprima.
- Se necessario, esci.
- Fai clic sulla casella di testo del nome utente. Viene visualizzata una finestra di dialogo.
- Seleziona l'account con cui vuoi accedere.
- Verifica la tua identità con il blocco schermo del dispositivo. Viene visualizzata la pagina
/home
e hai eseguito l'accesso.
7. Complimenti!
Hai completato questo codelab. Se hai domande, pubblicale nella mailing list FIDO-DEV o su Stack Overflow con il tag passkey
.