Panoramica
Ecco una panoramica generale dei passaggi chiave per la registrazione delle 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 archiviala sul server.
Le sezioni seguenti esaminano i dettagli di ogni passaggio.
Crea opzioni di creazione delle credenziali
Il primo passaggio da eseguire sul server è creare un oggetto PublicKeyCredentialCreationOptions
.
Per farlo, utilizza la libreria lato server FIDO. In genere offre una funzione di utilità che può creare queste opzioni per te. SimpleWebAuthn offre, ad esempio, generateRegistrationOptions
.
PublicKeyCredentialCreationOptions
deve includere tutto ciò che è necessario per la creazione della passkey: informazioni sull'utente, sul RP e una configurazione per le proprietà della credenziale che stai creando. Una volta definiti tutti questi elementi, passali in base alle necessità alla funzione nella libreria lato server FIDO responsabile della creazione dell'oggetto PublicKeyCredentialCreationOptions
.
Alcuni campi di PublicKeyCredentialCreationOptions
possono essere costanti. Gli altri devono essere definiti dinamicamente sul server:
rpId
: per compilare l'ID RP sul server, utilizza funzioni o variabili lato server che forniscono il nome host della tua applicazione web, ad esempioexample.com
.user.name
euser.displayName
:per compilare questi campi, utilizza le informazioni della sessione dell'utente che ha eseguito l'accesso (o le informazioni del nuovo account utente, se l'utente sta creando una passkey durante la registrazione).user.name
è in genere un indirizzo email ed è univoco per la persona responsabile.user.displayName
è un nome facile da usare. Tieni presente che non tutte le piattaforme utilizzanodisplayName
.user.id
: una stringa univoca e 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). Probabilmente hai già un ID utente nel tuo sistema, ma se necessario, creane uno specifico per le passkey per evitare che contenga PII.excludeCredentials
: un elenco di ID delle credenziali esistenti per impedire la duplicazione di una passkey dal provider di passkey. Per compilare questo campo, cerca nel database le credenziali esistenti per questo utente. Consulta i dettagli in Impedire la creazione di una nuova passkey se ne esiste già una.challenge
: per la registrazione delle credenziali, la sfida non è pertinente, a meno che tu non utilizzi l'attestazione, una tecnica più avanzata per verificare l'identità di un fornitore di passkey e i dati che emette. Tuttavia, anche se non utilizzi l'attestazione, la sfida è comunque un campo obbligatorio. Le istruzioni per creare una verifica sicura per l'autenticazione sono disponibili in Autenticazione con passkey lato server.
Codifica e decodifica

PublicKeyCredentialCreationOptions
inviato dal server. challenge
, user.id
e excludeCredentials.credentials
devono essere codificati lato server in base64URL
, in modo che PublicKeyCredentialCreationOptions
possa essere pubblicato tramite HTTPS.PublicKeyCredentialCreationOptions
includono campi che sono ArrayBuffer
, pertanto non sono 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 poi decodificati sul client.
- Sul server, la codifica e la decodifica vengono in genere gestite dalla libreria lato server FIDO.
- Sul client, al momento la codifica e la decodifica devono essere eseguite manualmente. In futuro sarà 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: crea opzioni di creazione delle credenziali
Nei nostri esempi utilizziamo la libreria SimpleWebAuthn. Qui, deleghiamo la creazione delle opzioni delle 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 = await 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 });
}
});
Memorizza la chiave pubblica

navigator.credentials.create
restituisce un oggetto PublicKeyCredential
.Quando navigator.credentials.create
viene risolto correttamente sul client, significa che è stata creata una passkey. Viene restituito un oggetto PublicKeyCredential
.
L'oggetto PublicKeyCredential
contiene un oggetto AuthenticatorAttestationResponse
, che rappresenta la risposta del fornitore di passkey all'istruzione del client di creare una passkey. Contiene informazioni sulla nuova credenziale che ti serve come RP per autenticare l'utente in un secondo momento. Scopri di più su AuthenticatorAttestationResponse
nell'appendice: AuthenticatorAttestationResponse
.
Invia l'oggetto PublicKeyCredential
al server. Una volta ricevuto, verifica il codice.
Trasferisci questo passaggio di verifica alla libreria lato server FIDO. In genere offre una funzione di utilità a questo scopo. SimpleWebAuthn offre, ad esempio, verifyRegistrationResponse
. Scopri cosa succede dietro le quinte in Appendice: verifica della risposta alla registrazione.
Una volta completata la verifica, memorizza le informazioni sulle credenziali nel tuo database in modo che l'utente possa autenticarsi in un secondo momento con la passkey associata a queste credenziali.
Utilizza 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 il Gestore delle password di Google.
Ecco un esempio di schema che puoi utilizzare per archiviare le informazioni sulle credenziali:
- Tabella Utenti:
user_id
: l'ID utente principale. Un ID univoco, permanente e casuale per l'utente. Utilizza questo valore come chiave primaria per la tabella Users.username
. Un nome utente definito dall'utente, potenzialmente modificabile.passkey_user_id
: l'ID utente senza PII specifico per la passkey, rappresentato dauser.id
nelle opzioni di registrazione. Quando l'utente tenta di autenticarsi in un secondo momento, l'autenticatore rende disponibile questopasskey_user_id
nella risposta di autenticazione inuserHandle
. Ti consigliamo di non impostarepasskey_user_id
come chiave primaria. Le chiavi primarie tendono a diventare PII di fatto nei sistemi, perché vengono utilizzate in modo esteso.
- Tabella Public key credentials (Credenziali della chiave pubblica):
id
: ID credenziali. Utilizza questo valore come chiave primaria per la tabella Credenziali di chiave pubblica.public_key
: chiave pubblica della credenziale.passkey_user_id
: utilizza questo campo come chiave esterna per stabilire un collegamento con la tabella Utenti.backed_up
: una passkey viene sottoposta a backup se viene sincronizzata dal provider di passkey. L'archiviazione dello stato del backup è utile se in futuro vuoi prendere in considerazione l'eliminazione delle password per gli utenti che dispongono di passkeybacked_up
. Puoi verificare se la passkey è stata sottoposta a backup esaminando il flag BE inauthenticatorData
o utilizzando una funzionalità della libreria lato server FIDO che in genere è disponibile per consentirti di accedere facilmente a queste informazioni. L'archiviazione dell'idoneità al backup può essere utile per rispondere a potenziali richieste degli utenti.name
: facoltativamente, un nome visualizzato per la credenziale per consentire agli utenti di assegnare nomi personalizzati alle credenziali.transports
: un array di trasporti. L'archiviazione dei trasporti è utile per l'esperienza utente di autenticazione. Quando i trasporti sono disponibili, il browser può comportarsi di conseguenza e mostrare un'interfaccia utente che corrisponde al trasporto utilizzato dal fornitore di passkey per comunicare con i client, in particolare per i casi d'uso di riautenticazione in cuiallowCredentials
non è vuoto.
Altre informazioni possono essere utili da memorizzare per migliorare l'esperienza utente, tra cui elementi come il fornitore della passkey, l'ora di creazione delle credenziali e l'ora dell'ultimo utilizzo. Scopri di più in Design dell'interfaccia utente delle passkey.
Esempio di codice: memorizzare la credenziale
Nei nostri esempi utilizziamo la libreria SimpleWebAuthn.
Qui, deleghiamo la verifica della risposta di registrazione alla sua 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 {
aaguid,
credentialPublicKey,
credentialID,
credentialBackedUp
} = registrationInfo;
// Name the credential based on AAGUID
const name =
aaguid === undefined ||
aaguid === '000000-0000-0000-0000-00000000' ?
req.useragent?.platform : aaguids[aaguid].name;
const base64CredentialID = isoBase64URL.fromBuffer(credentialID);
const base64PublicKey = isoBase64URL.fromBuffer(credentialPublicKey);
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: base64CredentialID,
passkey_user_id: user.passkey_user_id,
publicKey: base64PublicKey,
name,
aaguid,
transports: response.response.transports,
backed_up: credentialBackedUp,
registered_at: new Date().getTime()
});
// 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 sono i dati visualizzati dal browser. Contiene l'origine RP, la sfida eandroidPackageName
se il client è un'app per Android. In qualità di RP, la lettura diclientDataJSON
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 tu non utilizzi l'attestazione.authenticatorData
sono i dati visualizzati dal fornitore di passkey. In qualità di RP, la lettura diauthenticatorData
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:
- Le credenziali della chiave pubblica e un ID credenziali univoco.
- L'ID RP associato alla credenziale.
- Flag che descrivono lo stato dell'utente al momento della creazione della passkey: se l'utente era effettivamente presente e se la verifica dell'utente è stata eseguita correttamente (vedi approfondimento sulla verifica dell'utente).
- L'AAGUID è un identificatore del fornitore della passkey, ad esempio il Gestore delle password di Google. In base all'AAGUID, puoi identificare il fornitore della passkey e visualizzare il nome in una pagina di gestione delle passkey. (vedi Determinare il fornitore di passkey con AAGUID)
Anche se authenticatorData
è nidificato all'interno di attestationObject
, le informazioni che contiene sono necessarie per l'implementazione della passkey indipendentemente dal fatto che tu utilizzi o meno l'attestazione. authenticatorData
è codificato e contiene campi codificati in formato binario. In genere, l'analisi e la decodifica vengono gestite dalla libreria lato server. Se non utilizzi una libreria lato server, valuta la possibilità di utilizzare getAuthenticatorData()
lato client per risparmiare un po' di lavoro di analisi e decodifica lato server.
Appendice: verifica della risposta alla registrazione
A livello tecnico, la verifica della risposta di registrazione consiste nei seguenti controlli:
- Assicurati che l'ID RP 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
. - Il flag di presenza dell'utente
authenticatorData.up
in genere ètrue
, ma se la credenziale viene creata in modo condizionale, è previsto che siafalse
. - Verifica che il client sia stato in grado di fornire la sfida che gli hai assegnato. Se non utilizzi l'attestazione, questo controllo non è importante. Tuttavia, l'implementazione di questo controllo è una best practice: garantisce che il codice sia pronto se decidi di utilizzare l'attestazione in futuro.
- Assicurati che l'ID credenziale non sia ancora registrato per nessun utente.
- Verifica che l'algoritmo utilizzato dal fornitore di passkey per creare la credenziale sia un algoritmo che hai elencato (in ogni campo
alg
dipublicKeyCredentialCreationOptions.pubKeyCredParams
, che in genere è definito all'interno della libreria lato server e non è visibile). In questo modo, gli utenti possono registrarsi solo con gli algoritmi che hai scelto di consentire.
Per saperne di più, consulta il codice sorgente di SimpleWebAuthn per verifyRegistrationResponse
o esamina l'elenco completo delle verifiche nella specifica.