Z tego przewodnika dowiesz się, jak wdrożyć uwierzytelnianie powracających użytkowników za pomocą danych logowania możliwych do wykrycia, zarówno za pomocą wyraźnych przycisków, jak i płynnych sugestii autouzupełniania w przeglądarce (warunkowy interfejs użytkownika).
Po stronie serwera
Generowanie opcji
Utwórz punkt końcowy, który generuje parametry żądania WebAuthn przy użyciu sprawdzonej biblioteki zgodnie ze standardami.
- Użyj wstępnie zdefiniowanego identyfikatora RP: użyj wstępnie zdefiniowanego prawidłowego identyfikatora RP jako stałego ciągu znaków.
- Wygeneruj wyzwanie: wygeneruj bezpieczny kryptograficznie bufor losowy o wysokiej entropii, zapisz go bezpiecznie w sesji użytkownika i zakoduj jako Base64URL.
- Mapowanie danych logowania możliwych do wykrycia: określ pustą tablicę
[]dlaallowCredentials. W ten sposób żądane są dane logowania możliwe do wykrycia, co oznacza, że użytkownik nie musi najpierw wpisywać swojej nazwy użytkownika. Dostawca klucza dostępu wyświetli dostępne konta. - Poziom weryfikacji użytkownika: ustaw
userVerification: "preferred"(lub"required", jeśli jest to wyraźnie wymagane przez zasady zgodności firmy).- Żądany poziom ograniczenia
userVerificationMUSI być utrwalony w rekordzie sesji serwera w punkcie końcowym opcji, a nie przekazywany z powrotem od klienta za pomocą ciągów zapytania. Dzięki temu punkt końcowy weryfikacji może bezpiecznie egzekwować rygorystyczne ograniczenia dopasowania bez ryzyka manipulacji ze strony klienta.
- Żądany poziom ograniczenia
// Options generation example (discoverable flow)
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: "example.com",
allowCredentials: [], // Request discoverable passkeys
userVerification: "preferred",
};
// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";
Punkt końcowy weryfikacji
Bezpiecznie zweryfikuj asercję zwróconą przez klienta, aby uwierzytelnić użytkownika:
- Weryfikacja testu sesji: wymuszaj ścisłe dopasowanie testu między odpowiedzią klienta a oczekiwanym testem zapisanym w sesji.
- Enforce UV Preferences (Wymuszaj ustawienia UV):
- Zezwalaj na uwierzytelnianie bez weryfikacji użytkownika (np. gdy blokady ekranu uwierzytelniania są wyłączone), jeśli sesja
expectedUserVerificationżąda"preferred", przekazującrequireUserVerification: falsedo biblioteki weryfikacji po stronie serwera. Jeśli pojawi się prośba"required", wymagaj podania danych biometrycznych lub kodu PIN.
- Zezwalaj na uwierzytelnianie bez weryfikacji użytkownika (np. gdy blokady ekranu uwierzytelniania są wyłączone), jeśli sesja
- Błąd serwera 404: jeśli identyfikator danych logowania zwrócony przez klienta nie zostanie znaleziony w bazie danych, zwróć błąd HTTP
404, aby klient mógł wywołać interfejs Signal API.
Logika po stronie klienta
Adnotacja formularza HTML
Dodaj adnotacje do pól nazwy użytkownika i hasła, aby natywnie korzystać z warunkowego interfejsu użytkownika. Tokeny autouzupełniania łączą parametry specyfikacji webauthn, a autofokus uruchamia wyskakujące okienko autouzupełniania przeglądarki natychmiast po ustawieniu fokusu na polu wejściowym.
<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
<input
type="text"
name="username"
autocomplete="username webauthn"
autofocus
data-testid="username-field"
/>
<input type="password" name="password" autocomplete="current-password" />
<button type="submit">Sign in</button>
</form>
Explicit Button Flow
Uruchom uwierzytelnianie za pomocą klucza dostępu, gdy użytkownik kliknie przycisk „Zaloguj się za pomocą klucza dostępu”. Przed wywołaniem prośby o klucz dostępu przerwij wszystkie trwające wywołania autouzupełniania formularza (warunkowe żądanie GET).
Przepływ zapośredniczenia warunkowego (autouzupełnianie formularza)
Aktywuj sugestie autouzupełniania formularzy podczas wczytywania strony, aby oferować uwierzytelnianie za pomocą klucza dostępu, gdy użytkownicy skupiają się na polach logowania:
- Wykrywanie funkcji: wywołaj
PublicKeyCredential.getClientCapabilities()podczas wczytywania strony i pomiń logowanie się za pomocą klucza dostępu, jeśli funkcjaconditionalGetjest niedostępna. - Opcje dekodowania: dekoduj pobrany obiekt JSON z danymi logowania za pomocą
PublicKeyCredential.parseRequestOptionsFromJSON(). - Wywołaj warunkowe pobieranie: wywołaj
navigator.credentials.get()zmediation: "conditional"i przekaż sygnałAbortController. Spowoduje to zarejestrowanie autouzupełniania bez wyświetlania okna klucza dostępu. - Oddzielenie wyjątków try/catch: umieść wywołanie
navigator.credentials.getw bloku try/catch:NotAllowedError: użytkownik anulował prośbę o zalogowanie się za pomocą klucza dostępu lub upłynął limit czasu na odpowiedź.AbortError: żądanie uwierzytelnienia zostało anulowane automatycznie.
- Call Signal API: umieść wywołanie weryfikacji po stronie serwera
fetch()w bloku try/catch:- Wyświetl komunikat o błędzie, aby użytkownik wiedział, co poszło nie tak.
- Wywołaj
signalUnknownCredential()TYLKO wtedy, gdy serwer wyraźnie odpowie kodem stanu HTTP404(nie znaleziono danych logowania), a użytkownik nie jest uwierzytelniony. - Parametr
credentialIdprzekazywany dosignalUnknownCredential()MUSI być ciągiem znaków identyfikatora danych logowania zakodowanym w standardzie Base64URL (np.encoded.id), a NIE nieprzetworzonym obiektem ArrayBuffercredential.rawId.
- Zakoduj odpowiedź: zakoduj dane logowania
AuthenticatorAssertionResponseza pomocą.toJSON(), zanim wyślesz je na serwer w celu weryfikacji.
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";
let autofillAbortController = new AbortController();
async function initializeConditionalAutofill() {
// Feature detect Conditional Get autofill support
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true) {
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
try {
// Initiate Conditional UI form autofill suggestions
const credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
mediation: "conditional",
});
// Segregated verification fetch
const encoded = credential.toJSON();
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id,
});
}
}
} catch (err) {
// Silently swallow expected client WebAuthn exceptions
if (["NotAllowedError", "AbortError"].includes(err.name)) {
return;
}
console.error("Unexpected conditional get error:", err);
}
}
}
async function triggerButtonAuthentication() {
// Abort any pending Conditional Get call to prevent passkey prompt collisions
autofillAbortController.abort();
autofillAbortController = new AbortController(); // Reset controller for next triggers
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
let credential;
try {
// Passkey explicit prompt trigger
credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
});
} catch (err) {
if (err.name === "NotAllowedError") {
console.log("User cancelled passkey login.");
} else if (err.name === "AbortError") {
console.log("The authentication operation was aborted.");
}
// Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
initializeConditionalAutofill();
return; // Safe exit
}
// Segregated verification try/catch (HTTP 404 trigger)
const encoded = credential.toJSON();
try {
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Verification request error:", serverErr);
}
}
// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);
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';
Zastępcza synchronizacja interfejsu Signal API
Interfejs WebAuthn Signal API (webauthn-signals) to progresywna optymalizacja, która służy do synchronizowania menedżerów haseł ze stanem danych logowania na serwerze.
- Fallback Experience: Gated via
if (PublicKeyCredential.signalUnknownCredential). Jeśli nie jest obsługiwana, synchronizacja weryfikacji w tle jest pomijana bez zgłaszania wyjątków przeglądarki.
Ł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';