Ten przewodnik skupiający się na różnicach szczegółowo opisuje, jak wdrożyć uwierzytelnianie wielopoziomowe lub ponowną weryfikację zalogowanego użytkownika, zanim wprowadzi on poufne zmiany na koncie (np. aktualizacje haseł, przelewy finansowe).
Architektura Delta Flow
W odróżnieniu od zwykłego uwierzytelniania ponowne uwierzytelnianie za pomocą klucza dostępu ogranicza wyświetlanie okien dialogowych klucza dostępu wyłącznie do wstępnie zarejestrowanych danych logowania zalogowanego użytkownika, aby zapobiec mieszaniu kont lub fałszowaniu kluczy dostępu podczas aktywnych sesji.
Po stronie serwera
Delta generowania opcji
Utwórz punkt końcowy, który wypełnia parametry dozwolonych danych logowania, zwłaszcza w przypadku aktywnego, znanego użytkownika:
Ograniczanie danych logowania: wypełnij tablicę opcji allowCredentials określonymi rekordami PublicKeyCredentialDescriptor, które mapują wszystkie zarejestrowane identyfikatory danych logowania zalogowanego użytkownika. Pozostawienie tego pola pustego lub pominięcie go powoduje powrót do danych logowania możliwych do wykrycia, co narusza bezpieczeństwo sesji.
// Node.js step-up options generation example
router.post("/api/reauth/options", enforceActiveSession, async (req, res) => {
const userPasskeys = await db.findCredentialsByUserId(req.user.id);
const options = {
challenge: serverGeneratedBase64UrlChallenge, // Random challenge stored in user session
rpId: "example.com",
// Enforce allowance strictly limited to the user's credentials list
allowCredentials: userPasskeys.map((cred) => ({
type: "public-key",
id: cred.id,
transports: cred.transports, // Speeds up resolution by indicating platform transports
})),
};
return res.json(options);
});
Różnica w punktach końcowych weryfikacji
Zweryfikuj asercję zwróconą przez klienta:
Weryfikacja własności konta: punkt końcowy weryfikacji MUSI wyraźnie potwierdzić, że wynikowy uwierzytelniony identyfikator danych logowania zwrócony przez klienta odpowiada przechowywanemu rekordowi danych logowania, którego powiązany identyfikator użytkownika ściśle pasuje do aktywnego zalogowanego użytkownika (storedCredential.passkeyUserId === req.user.id). Jeśli zostanie zwrócony prawidłowy klucz dostępu innego użytkownika, uwierzytelnianie MUSI zostać natychmiast odrzucone.
Różnice w procesie po stronie klienta
Aplikacje wybierają jeden z 2 interfejsów ponownego uwierzytelniania w zależności od interfejsu transakcji:
A. Proces z przyciskiem (bez pól do wprowadzania danych)
Uruchom ponowne uwierzytelnianie, gdy użytkownik naciśnie przycisk „Potwierdź tożsamość” lub „Kontynuuj transakcję”.
<button id="reauth-btn" data-testid="reauth-button">Confirm Transaction</button>
let reauthAbortController = new AbortController();
async function triggerButtonReauth() {
// Abort any background suggestion flows to avoid passkey prompt collisions
reauthAbortController.abort();
reauthAbortController = new AbortController();
const optionsResponse = await fetch("/api/reauth/options", {
method: "POST",
});
const optionsJSON = await optionsResponse.json();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);
try {
const credential = await navigator.credentials.get({
publicKey,
signal: reauthAbortController.signal,
});
if (credential) {
const encodedCredential = credential.toJSON();
const verifyResponse = await fetch("/api/reauth/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(encodedCredential),
});
if (verifyResponse.ok) {
showTransactionSuccessUI();
} else if (verifyResponse.status === 404 && PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedCredential.id
});
}
}
} catch (err) {
if (err.name === "NotAllowedError") {
console.log("User cancelled reauthentication.");
}
}
}
document
.getElementById("reauth-btn")
.addEventListener("click", triggerButtonReauth);
Strategie zastępcze
Zastępcze wykrywanie funkcji klucza dostępu
Zawsze instaluj „webauthn-polyfills” i importuj je w kontekście.
Pamiętaj, że jeśli obsługiwany jest format PublicKeyCredential, obsługiwany jest też format PublicKeyCredential.getClientCapabilities.
import 'webauthn-polyfills';
Łatwa serializacja JSON – kreacja zastępcza
Zawsze instaluj „webauthn-polyfills” i importuj je w kontekście.
Jeśli obsługiwany jest format PublicKeyCredential, obsługiwane są też formaty PublicKeyCredential.parseRequestOptionsFromJSON i PublicKeyCredential.prototype.toJSON.
import 'webauthn-polyfills';