In deze handleiding wordt uitgelegd hoe je gebruikers hun geregistreerde toegangssleutels kunt laten bekijken, hernoemen en verwijderen, terwijl opgeslagen inloggegevens en gebruikersprofielgegevens gesynchroniseerd blijven tussen de server en de wachtwoordmanagers van de gebruiker via de Signal API.
Bewerkingen aan de serverzijde
Je backend-databaselaag en -eindpunten MOETEN algemene CRUD-acties ondersteunen voor geregistreerde inloggegevens en voor het updaten van gebruikersprofielgegevens. De server is losgekoppeld van frameworkspecifieke bibliotheken en biedt eindpunten voor het volgende:
- Alle inloggegevens van de gebruiker vermelden: Haal alle
StoredPasskeyCredential-records op die overeenkomen met de ID van de ingelogde gebruiker. - Namen van inloggegevens updaten: Accepteer een nieuwe aangepaste bijnaam voor een specifieke ID voor inloggegevens en bewaar de update op de server.
- Inloggegevens verwijderen: Verwijder een specifieke ID voor inloggegevens uit de database.
- Gebruikersaccountgegevens updaten: Accepteer geüpdatete waarden voor
name(gebruikersnaam) endisplayNamevoor het profiel van de ingelogde gebruiker, zodat de client deze kan synchroniseren met de toegangssleutelprovider.
// Node.js routing example for credential CRUD and user profile rename
router.get('/api/credentials', checkUserAuthenticated, async (req, res) => {
const list = await db.findCredentialsByUserId(req.user.id);
return res.json(list);
});
router.put('/api/credential/:id', checkUserAuthenticated, async (req, res) => {
const { id } = req.params;
const { name } = req.body;
const cred = await db.findCredentialById(id);
if (!cred || cred.passkeyUserId !== req.user.id) {
return res.status(404).json({ error: 'Credential not found.' });
}
cred.name = name;
await db.saveCredential(cred);
return res.json(cred);
});
router.delete('/api/credential/:id', checkUserAuthenticated, async (req, res) => {
const { id } = req.params;
const cred = await db.findCredentialById(id);
if (!cred || cred.passkeyUserId !== req.user.id) {
return res.status(404).json({ error: 'Credential not found.' });
}
await db.deleteCredential(id);
return res.json({ success: true });
});
router.put('/api/user', checkUserAuthenticated, async (req, res) => {
const { name, displayName } = req.body;
const updatedUser = await db.updateUser(req.user.id, { name, displayName });
return res.json(updatedUser);
});
Beheer-UI aan de clientzijde
Maak een speciaal instellingenvenster waarin gebruikers hun geregistreerde verificatieopties en accountprofielgegevens makkelijk kunnen controleren en beheren:
- Opgeslagen lijst tonen: Haal de lijst op van je eindpunt en geef afzonderlijke rijen met inloggegevens weer. Als de reactie leeg is, geef je een nuttig bericht voor een lege status weer (bijvoorbeeld 'Geen toegangssleutels gevonden').
- AAGUID-metadata toewijzen: Zoek voor elke toegangssleutel de eigenschap
aaguidop in je lokale register om de providergegevens weer te geven. Ga naar het gedeelte De toegangssleutelprovider bepalen op basis van de AAGUID voor meer informatie. - UI-vereisten per item: Elke rij in de lijstcontainer MOET het volgende weergeven:
- Icoon van aanbieder: Afbeelding of gegevens-URI afgeleid van AAGUID.
- Provider/aangepaste naam: Een van de AAGUID afgeleide naam of een door de gebruiker hernoemde tekenreeks.
- Registratiedatum: Het onbewerkte epoch-tijdstempel
registeredAtdat in de database is opgeslagen, is opgemaakt als een door mensen leesbare datum voor weergave aan de klant. - Datum van laatste gebruik: De onbewerkte epoch-tijdstempel
lastUsedAtdie in de database is opgeslagen, opgemaakt als een door mensen leesbare datum (indien aanwezig) voor weergave aan de klant. - Knop Naam wijzigen: Triggert een actie om de naam te wijzigen om de aangepaste bijnaam van de inloggegevens op de server te updaten (
PUT /api/credential/:id). Opmerking: De bijnaam van een individuele toegangssleutel wijzigen is een labelwijziging aan de serverzijde en roeptsignalCurrentUserDetails()NIET aan. - Knop Verwijderen: Hiermee wordt de verwijdering van de inloggegevens (
DELETE /api/credential/:id) geactiveerd en worden de resterende inloggegevens-ID's gesynchroniseerd viasignalAllAcceptedCredentials().
- UI voor naamswijziging van gebruikersaccount:
- Bied een bedieningselement waarmee de gebruiker de gebruikersnaam van het account (
name) ofdisplayName(PUT /api/user) kan updaten en synchroniseer de geüpdatete gebruikersgegevens onmiddellijk met de wachtwoordmanager viaPublicKeyCredential.signalCurrentUserDetails().
- Bied een bedieningselement waarmee de gebruiker de gebruikersnaam van het account (
- Voorwaardelijke knop Toegangssleutel maken:
- Bied een prominente registratietriggerknop Toegangssleutel maken aan op de beheerpagina. Voordat dit UI-element wordt gerenderd, MOET de pagina functies detecteren met
PublicKeyCredential.getClientCapabilities()om te verifiëren dat de platformverificator wordt ondersteund. Als toegangssleutels niet worden ondersteund, verberg je deze knop en moedig je gebruikers aan om zich in te schrijven voor standaard MFA. - Registratie van een beveiligingssleutel toestaan door
authenticatorSelection.authenticatorAttachmentweg te laten bij de aanroep vannavigator.credentials.create().
- Bied een prominente registratietriggerknop Toegangssleutel maken aan op de beheerpagina. Voordat dit UI-element wordt gerenderd, MOET de pagina functies detecteren met
Synchronisatie van Signal API
Met de Signal API kan de app de status van inloggegevens doorgeven aan wachtwoordmanagers, zodat de gesynchroniseerde kluizen van de gebruiker en je backend-database synchroon blijven.
- Regel voor functieherkenning en parametercodering:
- Voer altijd een functieherkenning uit voor Signal API-methoden voordat u ze aanroept (
if (PublicKeyCredential.signalAllAcceptedCredentials)enif (PublicKeyCredential.signalCurrentUserDetails)). - Alle parameters voor
userIden inloggegevens-ID die worden doorgegeven aan Signal API-methoden (signalAllAcceptedCredentials,signalCurrentUserDetails) MOETEN tekenreeksen met Base64URL-codering zijn.
- Voer altijd een functieherkenning uit voor Signal API-methoden voordat u ze aanroept (
- Synchronisatie van laden van pagina starten:
- De app MOET
signalAllAcceptedCredentials()automatisch aanroepen als de beheerpagina wordt geladen (bijvoorbeeld in eenDOMContentLoaded-gebeurtenislistener, module-initialisatie of frameworkcomponent-mount-hook zoalsuseEffect).
- De app MOET
- Synchronisatie van beheerupdates:
- De app MOET
signalAllAcceptedCredentials()onmiddellijk aanroepen in de click handler voor het verwijderen van inloggegevens na het ophalen. - De app MOET
signalCurrentUserDetails()onmiddellijk aanroepen binnen de clickhandler voor het wijzigen van de naam van je gebruikersaccount (nameofdisplayName) na het ophalen.
- De app MOET
// Client-side management synchronization ES module
import { listFetch, renameCredentialFetch, updateUserFetch, deleteFetch } from './api.js';
// Base64URL-encoded User ID string (illustration only)
const base64UrlUserId = "M2YPl-KGnA8";
async function syncAcceptedCredentials(currentCredentialsList) {
if (!window.PublicKeyCredential || !PublicKeyCredential.signalAllAcceptedCredentials) return;
try {
const credentialIds = currentCredentialsList.map(c => c.id); // Array of Base64URL credential ID strings
await PublicKeyCredential.signalAllAcceptedCredentials({
rpId, // RP ID must match the one defined on the server
userId: base64UrlUserId, // User ID Base64URL-encoded string
allAcceptedCredentialIds: credentialIds
});
} catch (e) {
console.error('SignalAllAcceptedCredentials sync failure:', e);
}
}
async function loadManagementPanel() {
const response = await listFetch();
const list = await response.json();
renderUI(list);
// Sync accepted credentials on initial load
await syncAcceptedCredentials(list);
}
async function performDelete(credentialId) {
const response = await deleteFetch(credentialId);
if (response.ok) {
const updatedResponse = await listFetch();
const updatedList = await updatedResponse.json();
renderUI(updatedList);
// Sync remaining accepted credentials after deletion
await syncAcceptedCredentials(updatedList);
}
}
// Renaming a passkey's nickname updates the server record only
async function performCredentialRename(credentialId, newCredentialName) {
const response = await renameCredentialFetch(credentialId, { name: newCredentialName });
if (response.ok) {
const updatedResponse = await listFetch();
renderUI(await updatedResponse.json());
}
}
// Renaming the user's account username or displayName triggers signalCurrentUserDetails
async function performUserRename(rpId, userId, updatedName, updatedDisplayName) {
const response = await updateUserFetch({ name: updatedName, displayName: updatedDisplayName });
if (response.ok && window.PublicKeyCredential && PublicKeyCredential.signalCurrentUserDetails) {
try {
await PublicKeyCredential.signalCurrentUserDetails({
rpId, // RP ID must match the one defined on the server
userId, // Base64URL-encoded user ID
name: updatedName, // Updated username
displayName: updatedDisplayName // Updated display name
});
} catch (e) {
console.error('SignalCurrentUserDetails sync failure:', e);
}
}
}
window.addEventListener('DOMContentLoaded', loadManagementPanel);
De toegangssleutelprovider bepalen op basis van de AAGUID
Een AAGUID (Authenticator Attestation Globally Unique Identifier) is een 128-bits ID die het model van de authenticator vertegenwoordigt, niet een specifieke instantie. Deze wordt opgenomen in de verificatorgegevens tijdens de registratie van de toegangssleutel en kan worden gebruikt om te bepalen welke toegangssleutelprovider (bijvoorbeeld Google Wachtwoordmanager, iCloud-sleutelhanger, 1Password) een inloggegeven heeft gemaakt.
De AAGUID mag alleen worden gebruikt om gebruikers te helpen met het beheer van toegangssleutels. De naam kan worden aangepast, tenzij deze cryptografisch is geattesteerd. Dit wordt op dit moment niet ondersteund door platformtoegangssleutels.
1. AAGUID-register
Een door de community onderhouden json-toewijzing van AAGUID's aan providernamen en -iconen is beschikbaar op:
https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json
Elke invoer heeft het volgende schema:
{
"<aaguid-uuid>": {
"name": "Provider Name",
"icon_light": "data:image/png;base64,...",
"icon_dark": "data:image/png;base64,..."
}
}
2. AAGUID gebruiken na registratie
Nadat je een registratiereactie hebt geverifieerd, lees je de aaguid uit het registratieresultaat en zoek je deze op in het register om de name en providerIcon van de inloggegevens in te vullen:
Voordat je de AAGUID opzoekt in het register, controleer je of deze gelijk is aan '00000000-0000-0000-0000-000000000000'. Als dat het geval is, sla je de registerzoekopdracht over en stel je name in op een fallback (bijv. de apparaatnaam van de user-agent of Onbekende toegangssleutelprovider) en providerIcon op undefined. Zoek alleen het register op voor niet-nul-AAGUID's.
import aaguids from './aaguids.json' with { type: 'json' };
const { aaguid } = registrationInfo;
if (aaguid === '00000000-0000-0000-0000-000000000000') {
// use the device name as the passkey provider based on
// the information derived from the user agent string,
// or just say "Unknown passkey provider"
} else {
const provider = aaguids[aaguid];
const credential = {
// ...other fields
aaguid,
name: provider?.name || 'Unknown passkey provider',
providerIcon: provider?.icon_light,
};
}
Reservestrategieën
Terugvaloptie voor detectie van toegangssleutelfunctie
Installeer altijd 'webauthn-polyfills' en importeer dit in de context.
Zolang PublicKeyCredential wordt ondersteund, wordt PublicKeyCredential.getClientCapabilities ook ondersteund.
import 'webauthn-polyfills';
Terugvaloptie voor synchronisatie van Signal API
Als de browser PublicKeyCredential.parseRequestOptionsFromJSON niet ondersteunt, gebruik je de webauthn-polyfills:
<script type="module">
if (!PublicKeyCredential.parseRequestOptionsFromJSON) {
await import('https://unpkg.com/webauthn-polyfills');
}
</script>
Hiermee wordt ook ondersteuning voor PublicKeyCredential.prototype.toJSON toegevoegd.