Panoramica
Di seguito è riportata una panoramica generale dei passaggi principali della registrazione tramite passkey:
- Definisci le opzioni per creare una passkey. Inviali al client in modo da poterli passare alla chiamata di creazione della passkey: la chiamata API WebAuthn
navigator.credentials.create
sul web ecredentialManager.createCredential
su Android. Dopo che l'utente conferma la creazione della passkey, la chiamata di creazione della passkey viene risolta e restituisce una credenzialePublicKeyCredential
. - Verifica la credenziale e memorizzala sul server.
Le sezioni seguenti approfondiscono le specifiche di ogni passaggio.
Opzioni per la creazione di credenziali
Il primo passaggio da eseguire sul server è creare un oggetto PublicKeyCredentialCreationOptions
.
A questo scopo, affidati alla libreria FIDO lato server. In genere offre una funzione di utilità in grado di creare queste opzioni per te. SimpleWebAuthn offre, ad esempio, generateRegistrationOptions
.
PublicKeyCredentialCreationOptions
deve includere tutto il necessario per la creazione della passkey: informazioni sull'utente, sulla parte soggetta a limitazioni e una configurazione per le proprietà della credenziale che stai creando. Una volta definiti tutti questi elementi, passali come necessario alla funzione nella tua libreria lato server FIDO responsabile della creazione dell'oggetto PublicKeyCredentialCreationOptions
.
Parte di PublicKeyCredentialCreationOptions
campi possono essere costanti. Altre devono essere definite dinamicamente sul server:
rpId
: per compilare l'ID RP sul server, utilizza funzioni o variabili lato server che ti forniscono il nome host della tua applicazione web, ad esempioexample.com
.user.name
euser.displayName
:per compilare questi campi, utilizza le informazioni relative alla sessione dell'utente che ha eseguito l'accesso (o i dati del nuovo account utente, se l'utente sta creando una passkey al momento della registrazione).user.name
è in genere un indirizzo email ed è univoco per la parte soggetta a limitazioni.user.displayName
è un nome semplice. Tieni presente che non tutte le piattaforme utilizzerannodisplayName
.user.id
: una stringa univoca casuale generata al momento della creazione dell'account. Deve essere permanente, a differenza di un nome utente che potrebbe essere modificabile. L'ID utente identifica un account, ma non deve contenere informazioni che consentono l'identificazione personale (PII). È probabile che tu abbia già un ID utente nel tuo sistema, ma se necessario, creane uno specifico per le passkey in modo che non contenga PII.excludeCredentials
: un elenco delle credenziali esistenti. ID per evitare la duplicazione di una passkey del fornitore di passkey. Per compilare questo campo, cerca nel database le credenziali esistenti di questo utente. Esamina i dettagli nell'articolo Impedire la creazione di una nuova passkey se ne esiste già una.challenge
: per la registrazione delle credenziali, la verifica non è pertinente, a meno che non utilizzi l'attestazione, una tecnica più avanzata per verificare l'identità di un provider di passkey e i dati che emette. Tuttavia, anche se non utilizzi l'attestazione, la verifica è comunque un campo obbligatorio. In questo caso, per semplicità, puoi impostare questa verifica su un singolo0
. Le istruzioni per creare una richiesta di verifica sicura per l'autenticazione sono disponibili in Autenticazione tramite passkey lato server.
Codifica e decodifica
PublicKeyCredentialCreationOptions
include campi ArrayBuffer
, che non sono quindi supportati da JSON.stringify()
. Ciò significa che, al momento, per pubblicare PublicKeyCredentialCreationOptions
tramite HTTPS, alcuni campi devono essere codificati manualmente sul server utilizzando base64URL
e quindi decodificati sul client.
- Sul server, la codifica e la decodifica, in genere, vengono gestite dalla libreria FIDO lato server.
- Sul client, la codifica e la decodifica devono essere eseguite manualmente al momento. In futuro diventerà più semplice: sarà disponibile un metodo per convertire le opzioni in formato JSON in
PublicKeyCredentialCreationOptions
. Controlla lo stato dell'implementazione in Chrome.
Codice di esempio: opzioni per la creazione delle credenziali
Nei nostri esempi utilizziamo la libreria SimpleWebAuthn. Qui, passiamo la creazione delle opzioni per le credenziali della chiave pubblica alla sua funzione generateRegistrationOptions
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
const { user } = res.locals;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// `excludeCredentials` prevents users from re-registering existing
// credentials for a given passkey provider
const excludeCredentials = [];
const credentials = Credentials.findByUserId(user.id);
if (credentials.length > 0) {
for (const cred of credentials) {
excludeCredentials.push({
id: isoBase64URL.toBuffer(cred.id),
type: 'public-key',
transports: cred.transports,
});
}
}
// Generate registration options for WebAuthn create
const options = generateRegistrationOptions({
rpName: process.env.RP_NAME,
rpID: process.env.HOSTNAME,
userID: user.id,
userName: user.username,
userDisplayName: user.displayName || '',
attestationType: 'none',
excludeCredentials,
authenticatorSelection: {
authenticatorAttachment: 'platform',
requireResidentKey: true
},
});
// Keep the challenge in the session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).send({ error: e.message });
}
});
Archivia la chiave pubblica
Quando navigator.credentials.create
si risolve correttamente sul client, significa che una passkey è stata creata. Viene restituito un oggetto PublicKeyCredential
.
L'oggetto PublicKeyCredential
contiene un oggetto AuthenticatorAttestationResponse
, che rappresenta la risposta del fornitore di passkey all'istruzione del cliente di creare una passkey. Contiene informazioni sulle nuove credenziali necessarie come parte soggetta a limitazioni per autenticare l'utente in un secondo momento. Scopri di più su AuthenticatorAttestationResponse
nell'Appendice: AuthenticatorAttestationResponse
.
Invia l'oggetto PublicKeyCredential
al server. Dopo averlo ricevuto, verificalo.
Consegna questo passaggio di verifica alla tua libreria lato server FIDO. A questo scopo, offre in genere una funzione di utilità. SimpleWebAuthn offre, ad esempio, verifyRegistrationResponse
. Scopri che cosa accade dietro le quinte nell'Appendice: verifica della risposta alla registrazione.
Una volta completata la verifica, memorizza le informazioni sulle credenziali nel database in modo che l'utente possa autenticarsi in un secondo momento con la passkey associata alla credenziale.
Usa una tabella dedicata per le credenziali della chiave pubblica associate alle passkey. Un utente può avere una sola password, ma può avere più passkey, ad esempio una passkey sincronizzata tramite il portachiavi iCloud di Apple e una tramite Gestore delle password di Google.
Di seguito è riportato uno schema di esempio che puoi utilizzare per memorizzare le informazioni sulle credenziali:
- Tabella Utenti:
- .
user_id
: l'ID utente principale. Un ID casuale univoco e permanente dell'utente. Utilizzala come chiave primaria per la tabella Utenti.username
Un nome utente definito dall'utente, potenzialmente modificabile.passkey_user_id
: l'ID utente privo di PII specifico per la passkey, rappresentato dauser.id
nelle opzioni di registrazione. Quando in un secondo momento l'utente tenterà di eseguire l'autenticazione, l'autenticatore renderà disponibilepasskey_user_id
nella sua risposta di autenticazione inuserHandle
. Ti consigliamo di non impostarepasskey_user_id
come chiave primaria. Le chiavi primarie tendono a diventare PII de facto nei sistemi, perché sono ampiamente utilizzate.
- Tabella delle credenziali della chiave pubblica:
- .
id
: ID credenziale. Utilizzala come chiave primaria per la tabella Credenziali chiave pubblica.public_key
: chiave pubblica della credenziale.passkey_user_id
: utilizzala come chiave esterna per stabilire un collegamento con la tabella Utenti.backed_up
: viene effettuato il backup di una passkey se è sincronizzata dal fornitore della passkey. La memorizzazione dello stato del backup è utile se vuoi prendere in considerazione la possibilità di perdere le password in futuro per gli utenti che detengono le passkeybacked_up
. Puoi verificare se il backup della passkey viene eseguito esaminando i flag inauthenticatorData
o utilizzando una funzionalità della libreria lato server FIDO, in genere disponibile per consentirti di accedere facilmente a queste informazioni. Memorizzare l'idoneità al backup può essere utile per rispondere alle potenziali richieste degli utenti.name
: facoltativamente, un nome visualizzato della credenziale per consentire agli utenti di assegnare nomi personalizzati alle credenziali.transports
: una serie di trasporti. L'archiviazione dei trasporti è utile per l'autenticazione dell'utente. Quando i trasporti sono disponibili, il browser può comportarsi di conseguenza e visualizzare una UI corrispondente al trasporto utilizzato dal provider di passkey per comunicare con i client, in particolare per i casi d'uso di riautenticazione in cui il campoallowCredentials
non è vuoto.
Possono essere utili altre informazioni da memorizzare ai fini dell'esperienza utente, tra cui elementi come il fornitore di passkey, l'ora di creazione delle credenziali e l'ora dell'ultimo utilizzo. Scopri di più nella pagina Design dell'interfaccia utente delle passkey.
Codice di esempio: memorizza la credenziale
Nei nostri esempi utilizziamo la libreria SimpleWebAuthn.
Qui, passiamo la verifica della risposta della registrazione alla relativa funzione verifyRegistrationResponse
.
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
const response = req.body;
// This sample code is for registering a passkey for an existing,
// signed-in user
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Verify the credential
const { verified, registrationInfo } = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
requireUserVerification: false,
});
if (!verified) {
throw new Error('Verification failed.');
}
const { credentialPublicKey, credentialID } = registrationInfo;
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: base64CredentialID,
publicKey: base64PublicKey,
// Optional: set the platform as a default name for the credential
// (example: "Pixel 7")
name: req.useragent.platform,
transports: response.response.transports,
passkey_user_id: user.passkey_user_id,
backed_up: registrationInfo.credentialBackedUp
});
// Kill the challenge for this session
delete req.session.challenge;
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).send({ error: e.message });
}
});
Appendice: AuthenticatorAttestationResponse
AuthenticatorAttestationResponse
contiene due oggetti importanti:
response.clientDataJSON
è una versione JSON dei dati client, che sul web rappresentano i dati visti dal browser. Contiene l'origine RP, la sfida eandroidPackageName
se il client è un'app per Android. In qualità di parte soggetta a limitazioni, la letturaclientDataJSON
ti consente di accedere alle informazioni visualizzate dal browser al momento della richiestacreate
.response.attestationObject
contiene due informazioni:attestationStatement
, che non è pertinente, a meno che non utilizzi l'attestazione.authenticatorData
: dati visualizzati dal provider di passkey. In qualità di parte soggetta a limitazioni, la letturaauthenticatorData
ti consente di accedere ai dati visualizzati dal fornitore di passkey e restituiti al momento della richiestacreate
.
authenticatorData
contiene informazioni essenziali sulla credenziale della chiave pubblica associata alla passkey appena creata:
- La credenziale della chiave pubblica e un ID credenziale univoco.
- L'ID parte soggetta a limitazioni associato alla credenziale.
- Flag che descrivono lo stato dell'utente al momento della creazione della passkey: se un utente era effettivamente presente e se l'utente è stato verificato (vedi
userVerification
). - AAGUID, che identifica il provider di passkey. La visualizzazione del fornitore di passkey può essere utile per i tuoi utenti, soprattutto se hanno una passkey registrata per il tuo servizio su più fornitori di passkey.
Anche se authenticatorData
è nidificato all'interno di attestationObject
, le informazioni che contiene sono necessarie per l'implementazione della passkey, indipendentemente dal fatto che utilizzi o meno l'attestazione. authenticatorData
è codificato e contiene campi codificati in formato binario. In genere la libreria lato server gestisce l'analisi e la decodifica. Se non utilizzi una libreria lato server, valuta la possibilità di utilizzare il lato client getAuthenticatorData()
per evitare di eseguire l'analisi e la decodifica del lavoro lato server.
Appendice: verifica della risposta alla registrazione
Di base, la verifica della risposta alla registrazione prevede i seguenti controlli:
- Assicurati che l'ID parte soggetta a limitazioni corrisponda al tuo sito.
- Assicurati che l'origine della richiesta sia un'origine prevista per il tuo sito (URL del sito principale, app per Android).
- Se richiedi la verifica dell'utente, assicurati che il flag di verifica dell'utente
authenticatorData.uv
siatrue
. Controlla che il flag della presenza dell'utenteauthenticatorData.up
siatrue
, poiché la presenza dell'utente è sempre obbligatoria per le passkey. - Verifica che il cliente sia stato in grado di rispondere alla sfida che gli hai dato. Se non utilizzi l'attestazione, questo controllo non è importante. Tuttavia, l'implementazione di questo controllo è una best practice in quanto garantisce che il codice sia pronto se decidi di utilizzare l'attestazione in futuro.
- Assicurati che l'ID credenziali non sia ancora registrato per nessun utente.
- Verifica che l'algoritmo utilizzato dal provider di passkey per creare la credenziale sia un algoritmo elencato da te (in ogni campo
alg
dipublicKeyCredentialCreationOptions.pubKeyCredParams
, che in genere viene definito all'interno della libreria lato server e non puoi vederti). In questo modo ti assicuri che gli utenti possano registrarsi solo con gli algoritmi che hai scelto di consentire.
Per scoprire di più, consulta il codice sorgente per verifyRegistrationResponse
di SimpleWebAuthn o consulta l'elenco completo delle verifiche nella specifica.