Panoramica
Ecco una panoramica generale dei passaggi principali della registrazione delle passkey:
- Definisci le opzioni per creare una passkey. Inviale al client in modo da passarle alla chiamata per la 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 per la creazione della passkey viene risolta e restituisce una credenzialePublicKeyCredential
. - Verifica la credenziale e memorizzala sul server.
Le sezioni seguenti illustrano le specifiche di ciascun passaggio.
Opzioni di creazione delle credenziali
Il primo passaggio da eseguire sul server è creare un oggetto PublicKeyCredentialCreationOptions
.
Per farlo, utilizza la tua 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 serve per la creazione della passkey: informazioni sull'utente, sulla parte soggetta a limitazioni e una configurazione delle proprietà della credenziale che stai creando. Dopo aver definito tutti questi elementi, passali, se necessario, alla funzione nella libreria lato server FIDO responsabile della creazione dell'oggetto PublicKeyCredentialCreationOptions
.
Alcuni dei campi di PublicKeyCredentialCreationOptions
possono essere costanti. Altre devono essere definite in modo dinamico sul server:
rpId
: per compilare l'ID RP sul server, utilizza le funzioni o le variabili lato server che indicano il nome host della tua applicazione web, ad esempioexample.com
.user.name
euser.displayName
:per compilare questi campi, utilizza le informazioni sulla 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 facile da usare. Tieni presente che non tutte le piattaforme utilizzerannodisplayName
.user.id
: una stringa univoca e casuale generata al momento della creazione dell'account. Questo nome deve essere permanente, a differenza di un nome utente modificabile. Lo User-ID 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 PII.excludeCredentials
: un elenco di ID delle credenziali esistenti per impedire la duplicazione di una passkey del provider. Per compilare questo campo, cerca le credenziali esistenti di questo utente nel database. Rivedi i dettagli nella pagina 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, puoi impostare questa sfida su un singolo0
per semplicità. Le istruzioni per creare una verifica di sicurezza per l'autenticazione sono disponibili in Autenticazione tramite passkey lato server.
Codifica e decodifica
![PublicKeyCredentialCreationOptions inviate dal server](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-registration/images/server-publicKeyCredentialCreationOptions.png?authuser=7&hl=it)
PublicKeyCredentialCreationOptions
inviata dal server. challenge
, user.id
e excludeCredentials.credentials
devono essere codificati sul lato server in base64URL
, in modo che PublicKeyCredentialCreationOptions
possano essere pubblicati tramite HTTPS.PublicKeyCredentialCreationOptions
include campi che sono ArrayBuffer
, pertanto non sono supportati da JSON.stringify()
. Ciò significa che, al momento, per consegnare 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 generalmente gestite dalla libreria lato server FIDO.
- Sul client, la codifica e la decodifica devono essere eseguite manualmente al momento. In futuro diventerà più semplice: sarà disponibile un metodo per convertire opzioni come JSON in
PublicKeyCredentialCreationOptions
. Controlla lo stato dell'implementazione in Chrome.
Codice di esempio: crea opzioni di creazione delle credenziali
Utilizziamo la libreria SimpleWebAuthn nei nostri esempi. Qui passiamo la creazione di opzioni di credenziali di chiave pubblica alla relativa 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
![PublicKeyCredentialCreationOptions inviate dal server](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-registration/images/server-publicKeyCredentialCreationOptions.png?authuser=7&hl=it)
navigator.credentials.create
restituisce un oggetto PublicKeyCredential
.Se 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 provider di passkey all'istruzione del client di creare una passkey. Contiene informazioni sulla nuova credenziale necessaria come RP 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.
Passa questo passaggio di verifica alla tua libreria lato server FIDO. In genere offre una funzione di utilità a questo scopo. SimpleWebAuthn offre, ad esempio, verifyRegistrationResponse
. Scopri cosa succede in dettaglio nell'Appendice: verifica della risposta alla registrazione.
Una volta che la verifica ha esito positivo, archivia le informazioni sulle credenziali nel tuo database in modo che l'utente possa eseguire l'autenticazione in seguito con la passkey associata alla credenziale in questione.
Usa una tabella dedicata per le credenziali di 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.
Ecco uno schema di esempio che puoi utilizzare per archiviare 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 senza PII specifico per passkey, rappresentato dauser.id
nelle opzioni di registrazione. Quando l'utente tenterà in un secondo momento di autenticarsi, l'autenticatore renderà disponibile questopasskey_user_id
nella sua risposta di autenticazione inuserHandle
. Ti consigliamo di non impostarepasskey_user_id
come chiave primaria. Le chiavi primarie tendono a diventare di fatto PII nei sistemi, perché sono ampiamente utilizzate.
- Tabella Credenziali chiave pubblica:
id
: ID credenziali. 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
: se la passkey è sincronizzata dal provider della passkey, viene eseguito il backup. La memorizzazione dello stato del backup è utile se vuoi prendere in considerazione l'eliminazione delle password in futuro per gli utenti che dispongono di passkeybacked_up
. Puoi verificare se è stato eseguito il backup della passkey esaminando i flag inauthenticatorData
oppure utilizzando una funzionalità di libreria lato server FIDO, in genere disponibile, per accedere facilmente a queste informazioni. Memorizzare l'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 sono disponibili i trasporti, il browser può comportarsi di conseguenza e visualizzare un'interfaccia utente che corrisponde al trasporto che il provider di passkey utilizza per comunicare con i client, in particolare per i casi d'uso di riautenticazione in cui il campoallowCredentials
non è vuoto.
Può essere utile memorizzare altre informazioni ai fini dell'esperienza utente, ad esempio elementi come il fornitore della passkey, l'ora di creazione delle credenziali e l'ora dell'ultimo utilizzo. Scopri di più nel design dell'interfaccia utente delle passkey.
Codice di esempio: memorizza la credenziale
Utilizziamo la libreria SimpleWebAuthn nei nostri esempi.
Qui passiamo la verifica della risposta di 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 sono dati come vengono visti dal browser. Contiene l'origine della parte soggetta a limitazioni, il challenge 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 richiesta dicreate
.response.attestationObject
contiene due informazioni:attestationStatement
, che non è pertinente, a meno che non utilizzi l'attestazione.authenticatorData
include i dati rilevati dal fornitore della passkey. In qualità di parte soggetta a limitazioni, la lettura diauthenticatorData
ti consente di accedere ai dati visualizzati dal fornitore di passkey e restituiti al momento della richiesta dicreate
.
authenticatorData
contiene informazioni essenziali sulla credenziale della chiave pubblica associata alla passkey appena creata:
- La credenziale della chiave pubblica stessa e un rispettivo ID univoco.
- L'ID parte soggetta a limitazioni associato alla credenziale.
- Flag che descrivono lo stato dell'utente al momento della creazione della passkey, ovvero se un utente era effettivamente presente e se è stato verificato (vedi
userVerification
). - AAGUID, che identifica il provider di passkey. Visualizzare il fornitore di passkey può essere utile per i tuoi utenti, soprattutto se hanno una passkey registrata per il tuo servizio su più provider 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 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 sfruttare il lato client di getAuthenticatorData()
per evitare alcune attività di analisi e decodifica sul lato server.
Appendice: verifica della risposta alla registrazione
In fondo, la verifica della risposta alla registrazione consiste nei seguenti controlli:
- Assicurati che l'ID della 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
. Verifica 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 suggerire la sfida che hai proposto. 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 provider di passkey per creare la credenziale sia un algoritmo che hai elencato (in ogni campo
alg
dipublicKeyCredentialCreationOptions.pubKeyCredParams
, in genere definito all'interno della tua libreria lato server e non visibile da te). In questo modo ti assicuri che gli utenti possano registrarsi soltanto con gli algoritmi che hai scelto di consentire.
Per saperne di più, consulta il codice sorgente di verifyRegistrationResponse
di SimpleWebAuthn o consulta l'elenco completo delle verifiche nella specifica.