Klucze dostępu sprawiają, że konta użytkowników są bezpieczniejsze, prostsze i łatwiejsze w użyciu.
Korzystanie z kluczy dostępu zamiast haseł to świetny sposób na to, aby witryny były bezpieczniejsze, prostsze, łatwiejsze w użyciu i nie wymagały haseł. Dzięki kluczowi dostępu użytkownik może logować się na stronie lub w aplikacji za pomocą odcisku palca, skanu twarzy lub kodu PIN urządzenia.
Klucz dostępu musi zostać utworzony, powiązany z kontem użytkownika i związany z kluczem publicznym na serwerze, zanim użytkownik będzie mógł się zalogować.
Jak to działa
Użytkownik może zostać poproszony o utworzenie klucza dostępu w jednym z tych scenariuszy:
- Gdy użytkownik loguje się za pomocą hasła.
- Gdy użytkownik loguje się za pomocą klucza dostępu z innego urządzenia (czyli
authenticatorAttachment
jestcross-platform
). - Na specjalnej stronie, na której użytkownicy mogą zarządzać kluczami.
Aby utworzyć klucz dostępu, użyj interfejsu WebAuthn API.
Cztery elementy procesu rejestracji klucza:
- Back-end: serwer back-end, na którym znajduje się baza danych kont zawierająca klucz publiczny i inne metadane klucza dostępu.
- Frontend: frontend, który komunikuje się z przeglądarką i wysyła do backendu żądania fetch.
- Przeglądarka: przeglądarka użytkownika, w której działa kod JavaScript.
- Authenticator: aplikacja uwierzytelniająca użytkownika, która tworzy i przechowuje klucz dostępu. Może to obejmować menedżera haseł na tym samym urządzeniu co przeglądarka (np. gdy używasz Windows Hello) lub na innym urządzeniu, takim jak telefon.
Dodawanie nowego klucza dostępu do istniejącego konta użytkownika:
- Użytkownik loguje się na stronie.
- Po zalogowaniu się użytkownik prosi o utworzenie klucza dostępu na interfejsie, na przykład przez naciśnięcie przycisku „Utwórz klucz dostępu”.
- Frontend wysyła żądanie o informacje do backendu, aby utworzyć klucz dostępu, takie jak informacje o użytkowniku, wyzwanie i identyfikatory danych uwierzytelniających do wykluczenia.
- Frontend wywołuje
navigator.credentials.create()
, aby utworzyć klucz dostępu. To wywołanie zwraca obietnicę. - Klucz dostępu jest tworzony po wyrażeniu przez użytkownika zgody za pomocą blokady ekranu urządzenia. Obietnica jest rozwiązywana, a klucz publiczny jest zwracany do interfejsu.
- Frontend wysyła dane logowania z kluczem publicznym do backendu i przechowuje identyfikator danych logowania oraz klucz publiczny powiązany z kontem użytkownika na potrzeby przyszłych uwierzytelnień.
Zgodność
WebAuthn jest obsługiwany przez większość przeglądarek, ale występują pewne niewielkie luki. Aby dowiedzieć się, jakie kombinacje przeglądarek i systemów operacyjnych obsługują tworzenie kluczy dostępu, odwiedź stronę Obsługa urządzeń – passkeys.dev.
Tworzenie nowego klucza dostępu
Oto jak frontend powinien działać po otrzymaniu żądania utworzenia nowego klucza dostępu.
Wykrywanie cech
Zanim wyświetlisz przycisk „Utwórz nowy klucz dostępu”, sprawdź, czy:
- Przeglądarka obsługuje WebAuthn z
PublicKeyCredential
.
- Urządzenie obsługuje uwierzytelnianie na platformie (może tworzyć klucze dostępu i uwierzytelniać się za ich pomocą) za pomocą
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.
- Przeglądarka obsługuje warunkowy interfejs użytkownika WebAuthn z użyciem
PublicKeyCredenital.isConditionalMediationAvailable()
.
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.
// `isConditionalMediationAvailable` means the feature detection is usable.
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
// Check if user verifying platform authenticator is available.
Promise.all([
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
PublicKeyCredential.isConditionalMediationAvailable(),
]).then(results => {
if (results.every(r => r === true)) {
// Display "Create a new passkey" button
}
});
}
Dopóki nie zostaną spełnione wszystkie warunki, klucze dostępu nie będą obsługiwane w tej przeglądarce. Do tego czasu przycisk „Utwórz nowy klucz dostępu” nie powinien się wyświetlać.
Pobieranie ważnych informacji z back-endu
Gdy użytkownik kliknie przycisk, pobierz ważne informacje do wywołania funkcjinavigator.credentials.create()
z poziomu backendu:
challenge
: wyzwanie wygenerowane przez serwer w ArrayBuffer dla tej rejestracji. Jest on wymagany, ale nieużywany podczas rejestracji, chyba że przeprowadzasz weryfikację – zaawansowany temat, który nie jest tutaj omawiany.user.id
: unikalny identyfikator użytkownika. Ta wartość musi być tablicą ArrayBuffer, która nie zawiera informacji umożliwiających identyfikację, takich jak adresy e-mail czy nazwy użytkowników. Na potrzeby tego testu wystarczy losowa wartość 16-bajtowa wygenerowana dla każdego konta.user.name
: to pole powinno zawierać unikalny identyfikator konta, który użytkownik będzie w stanie rozpoznać, np. adres e-mail lub nazwę użytkownika. Będzie ona widoczna w selecterze kont. (jeśli używasz nazwy użytkownika, użyj tej samej wartości co w przypadku uwierzytelniania za pomocą hasła).user.displayName
: to pole jest wymagane. Jest to bardziej przyjazna dla użytkownika nazwa konta. Nie musi być niepowtarzalna i może być nazwą wybraną przez użytkownika. Jeśli Twoja witryna nie ma odpowiedniej wartości do uwzględnienia tutaj, prześlij pusty ciąg znaków. W zależności od przeglądarki może się on wyświetlać w selektorze kont.excludeCredentials
: zapobiega rejestrowaniu tego samego urządzenia, podając listę już zarejestrowanych identyfikatorów danych logowania. Elementtransports
, jeśli jest podany, powinien zawierać wynik wywołania funkcjigetTransports()
podczas rejestracji poszczególnych danych logowania.
Wywołanie interfejsu WebAuthn API w celu utworzenia klucza dostępu
Aby utworzyć nowy klucz dostępu, zadzwoń pod numer navigator.credentials.create()
. Interfejs API zwraca obietnicę, czekając na interakcję użytkownika w ramach okna modalnego.
const publicKeyCredentialCreationOptions = {
challenge: *****,
rp: {
name: "Example",
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
};
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});
// Encode and send the credential to the server for verification.
Parametry, których nie wyjaśniono powyżej:
rp.id
: identyfikator RP to domena, a strona internetowa może wskazywać albo swoją domenę, albo sufiks, który można zarejestrować. Jeśli na przykład źródło RP tohttps://login.example.com:1337
, identyfikator RP może byćlogin.example.com
lubexample.com
. Jeśli identyfikator RP toexample.com
, użytkownik może uwierzytelnić się na domenielogin.example.com
lub na dowolnych subdomenach domenyexample.com
.rp.name
: nazwa RP.pubKeyCredParams
: to pole określa obsługiwane przez RP algorytmy kluczy publicznych. Zalecamy ustawienie go na[{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]
. Określa ona obsługę ECDSA z P-256 i RSA PKCS#1. Obsługa tych algorytmów zapewnia pełne pokrycie.authenticatorSelection.authenticatorAttachment
: ustaw tę opcję na"platform"
, jeśli utworzenie klucza dostępu jest uaktualnieniem z hasła, np. w ramach promocji po zalogowaniu się."platform"
wskazuje, że RP chce uwierzytelniacza platformy (uwierzytelniacza wbudowanego w urządzenie platformy), który nie wyświetla prośby o włożenie np. klucza bezpieczeństwa USB. Użytkownik ma prostszą opcję tworzenia klucza dostępu.authenticatorSelection.requireResidentKey
: ustaw tę opcję na wartość logiczną „true” (prawda). Dane logowania umożliwiające ich znalezienie (klucz rezydencki) przechowują informacje o użytkowniku w kluczu dostępu i pozwalają użytkownikom wybrać konto podczas uwierzytelniania. Więcej informacji o danych logowania, które można znaleźć, znajdziesz w artykule Dane logowania, które można znaleźćauthenticatorSelection.userVerification
: wskazuje, czy weryfikacja użytkownika za pomocą blokady ekranu urządzenia jest ustawiona na"required"
,"preferred"
lub"discouraged"
. Wartość domyślna to"preferred"
, co oznacza, że uwierzytelniacz może pominąć weryfikację użytkownika. Ustaw tę wartość na"preferred"
lub pomiń tę właściwość.
Prześlij zwrócone przez usługę API klucze publiczne do backendu
Gdy użytkownik wyrazi zgodę, używając blokady ekranu urządzenia, tworzony jest klucz dostępu, a obietnica jest rozwiązywana, zwracając obiekt PublicKeyCredential do interfejsu.
Obietnice mogą zostać odrzucone z różnych powodów. Te błędy możesz naprawić, sprawdzając właściwość name
obiektu Error
:
InvalidStateError
: klucz dostępu jest już na urządzeniu. Użytkownik nie zobaczy żadnego okna błędu, a witryna nie powinna traktować tego jako błędu – użytkownik chciał zarejestrować urządzenie lokalne i to się stało.NotAllowedError
: operacja została anulowana przez użytkownika.- Inne wyjątki: wystąpił nieoczekiwany błąd. Przeglądarka wyświetla użytkownikowi okno błędu.
Obiekt danych uwierzytelniających klucz publiczny zawiera te właściwości:
id
: identyfikator utworzonego klucza dostępu zakodowany w formacie Base64URL. Ten identyfikator pomaga przeglądarce określić, czy po uwierzytelnieniu na urządzeniu znajduje się pasujący klucz dostępu. Ta wartość musi być przechowywana w bazie danych na serwerze.rawId
: wersja ArrayBuffer identyfikatora danych logowania.response.clientDataJSON
: dane klienta zakodowane w ArrayBuffer.response.attestationObject
: Zarys obiektu uwierzytelniania zakodowanego w ArrayBuffer. Zawiera on ważne informacje, takie jak identyfikator RP, flagi i klucz publiczny.authenticatorAttachment
: zwraca"platform"
, gdy te dane uwierzytelniające są tworzone na urządzeniu obsługującym klucze dostępu.type
: to pole ma zawsze wartość"public-key"
.
Jeśli do obsługi obiektu danych logowania z kluczem publicznym na zapleczu używasz biblioteki, zalecamy wysłanie całego obiektu na zaplecze po częściowym zakodowaniu go za pomocą base64url.
Zapisywanie danych logowania
Po otrzymaniu danych logowania na kluczu publicznym na zapleczu prześlij je do biblioteki FIDO w celu przetworzenia obiektu.
Następnie możesz zapisać informacje pobrane z danych logowania w bazie danych na potrzeby przyszłego użycia. Na liście poniżej znajdziesz typowe właściwości do zapisania:
- Identyfikator danych logowania (klucz podstawowy)
- User-ID
- Klucz publiczny
Uwierzytelnienie klucza publicznego zawiera też te informacje, które możesz zapisać w bazie danych:
- Flaga dostępności kopii zapasowej:
true
jeśli urządzenie kwalifikuje się do synchronizacji klucza dostępu. - Flaga stanu kopii zapasowej:
true
jeśli utworzony klucz dostępu jest rzeczywiście ustawiony do synchronizacji. - Transporty:
Lista transportów obsługiwanych przez urządzenie:
"internal"
oznacza, że urządzenie obsługuje klucz dostępu,"hybrid"
oznacza, że urządzenie obsługuje też uwierzytelnianie na innym urządzeniu.
Szczegółowe instrukcje znajdziesz w artykule Rejestracja klucza dostępu po stronie serwera.
Aby uwierzytelnić użytkownika, przeczytaj artykuł Logowanie za pomocą klucza za pomocą funkcji autouzupełniania.