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 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 z jego kluczem publicznym przechowywanym 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:

  • Backend: serwer backendowy, na którym znajduje się baza danych kont zawierająca klucz publiczny i inne metadane dotyczące 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.
Schemat rejestracji klucza dostępu

Dodawanie nowego klucza dostępu do istniejącego konta użytkownika:

  1. Użytkownik loguje się na stronie.
  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. 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.
  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 zgody przez użytkownika za pomocą blokady ekranu urządzenia. Obietnica jest rozwiązywana, a klucz publiczny jest zwracany do interfejsu.
  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 uwierzytelnianie na platformie (może tworzyć klucze dostępu i uwierzytelniać się za ich pomocą) 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. (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. Element transports, jeśli jest podany, powinien zawierać wynik wywołania funkcji getTransports() 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 oknie modalnym.

Obsługa przeglądarek

  • Chrome: 60.
  • Edge: 18.
  • 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 strona internetowa może wskazywać albo swoją domenę, albo sufiks, który można zarejestrować. 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 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 na wybór konta po uwierzytelnieniu. 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 uwierzytelnienia zakodowanego 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 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:

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 formularzy.

Zasoby