Tworzenie klucza dostępu do logowania się bez hasła

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 zalogować się na stronie lub w aplikacji za pomocą odcisku palca, wizerunku 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 jest cross-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 żądania pobierania do backendu.
  • 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.
Schemat rejestracji klucza dostępu

Proces dodawania nowego klucza dostępu do dotychczasowego konta użytkownika wygląda tak:

  1. Użytkownik loguje się w witrynie.
  2. 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”.
  3. Interfejs prosi o informacje z backendu w celu utworzenia klucza dostępu, takiego jak informacje o użytkowniku, test zabezpieczający i identyfikatory danych logowania do wykluczenia.
  4. Frontend wywołuje funkcję navigator.credentials.create(), aby utworzyć klucz dostępu. To wywołanie zwraca obietnicę.
  5. Klucz dostępu jest tworzony po wyrażeniu przez użytkownika zgody za pomocą blokady ekranu. Obietnica została rozwiązana, a do frontendu zwrócone są dane uwierzytelniające klucza publicznego.
  6. 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.

Obsługa przeglądarek

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Źródło

  • Urządzenie obsługuje usługę uwierzytelniającą platformy (może utworzyć klucz dostępu i uwierzytelnić się za pomocą klucza dostępu) za pomocą PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().

Obsługa przeglądarek

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Źródło

Obsługa przeglądarek

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Źródło

// 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 tego 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. (W przypadku nazwy użytkownika użyj takiej samej wartości jak podczas uwierzytelniania 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. Ta informacja może się wyświetlać w selektorze kont w zależności od przeglądarki.
  • excludeCredentials: zapobiega rejestrowaniu tego samego urządzenia, podając listę już zarejestrowanych identyfikatorów danych logowania. Element transports (jeśli został podany) powinien zawierać wynik wywołania getTransports() podczas rejestracji poszczególnych danych logowania.

Wywołaj interfejs WebAuthn API, aby utworzyć klucz dostępu

Aby utworzyć nowy klucz dostępu, zadzwoń pod numer navigator.credentials.create(). Interfejs API zwraca obietnicę, która czeka na interakcję użytkownika i wyświetla okno modalne.

Obsługa przeglądarek

  • Chrome: 60.
  • Edge: 18 lat.
  • Firefox: 60.
  • Safari: 13.

Źródło

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 witryna może określić swoją domenę lub sufiks możliwy do zarejestrowania. Jeśli na przykład źródło RP to https://login.example.com:1337, identyfikator RP może być login.example.com lub example.com. Jeśli identyfikator RP to example.com, użytkownik może uwierzytelnić się na domenie login.example.com lub na dowolnych subdomenach domeny example.com.

  • rp.name: nazwa RP.

  • pubKeyCredParams: to pole określa obsługiwane przez RP algorytmy kluczy publicznych. Zalecamy ustawienie jej na [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. Określa ona obsługę ECDSA z P-256 i RSA PKCS#1. Ich obsługa 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" oznacza, że w RPA jest wymagany moduł uwierzytelniający platformy (umieszczony na platformie), który nie wyświetla prośby o podłączenie 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 uwierzytelnianie może pominąć weryfikację użytkownika. Ustaw tę wartość na "preferred" lub pomiń tę właściwość.

Wyślij zwrócone dane logowania klucza publicznego do backendu

Gdy użytkownik wyrazi zgodę przy użyciu blokady ekranu urządzenia, tworzony jest klucz dostępu, a obietnica zostaje zrealizowana przez zwrócenie obiektu PublicKeyCredential do frontendu.

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 już to zrobił.
  • NotAllowedError: użytkownik anulował operację.
  • 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 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 identyfikatora danych logowania w formacie ArrayBuffer.
  • response.clientDataJSON: dane klienta zakodowane w formacie ArrayBuffer.
  • response.attestationObject: obiekt uwierzytelnienia zakodowany w ArrayBuffer. Zawiera on ważne informacje, takie jak identyfikator RP, flagi i klucz publiczny.
  • authenticatorAttachment: zwraca wartość "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 używasz biblioteki do obsługi obiektu danych logowania klucza publicznego w backendzie, zalecamy wysłanie całego obiektu do backendu 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 przyszłość. Na liście poniżej znajdziesz typowe właściwości do zapisania:

  • Identyfikator danych logowania (klucz podstawowy)
  • User-ID
  • Klucz publiczny

Dane logowania klucza publicznego zawierają też te informacje, które warto zapisać w bazie danych:

Szczegółowe instrukcje znajdziesz w artykule Rejestracja klucza dostępu po stronie serwera.

Aby uwierzytelnić użytkownika, przeczytaj artykuł Logowanie się za pomocą klucza dostępu przez autouzupełnianie formularzy.

Zasoby