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

Klucze dostępu zwiększają bezpieczeństwo kont użytkowników i ułatwiają ich obsługę.

Używanie kluczy dostępu zamiast haseł to świetny sposób na zwiększenie bezpieczeństwa, prostoty, łatwiejszego użycia i bez hasła w witrynach. 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.

Aby użytkownik mógł się zalogować, klucz publiczny musi być utworzony i powiązany z kontem użytkownika oraz zapisać jego klucz publiczny na serwerze.

Jak to działa

Prośba o utworzenie klucza dostępu może pojawić się w jednej z tych sytuacji:

  • 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 to cross-platform).
  • Na specjalnej stronie, na której użytkownicy mogą zarządzać swoimi kluczami dostępu.

Aby utworzyć klucz dostępu, użyj interfejsu WebAuthn API.

4 składniki procesu rejestracji klucza:

  • Backend: serwer backendu, w którym znajduje się baza danych kont, w której jest przechowywany klucz publiczny i inne metadane dotyczące 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, która uruchomi Twój JavaScript.
  • Authenticator: aplikacja uwierzytelniająca użytkownika, która tworzy i przechowuje klucz dostępu. Może to znajdować się na tym samym urządzeniu co przeglądarka (np. za pomocą Windows Hello) lub na innym urządzeniu, na przykład na telefonie.
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 we frontendzie, np. 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 navigator.credentials.create(), aby utworzyć klucz dostępu. To wywołanie zwraca obietnicę.
  5. Klucz dostępu jest tworzony, gdy użytkownik wyrazi zgodę za pomocą blokady ekranu urządzenia. Obietnica została rozwiązana, a do frontendu zwrócone są dane uwierzytelniające klucza publicznego.
  6. Frontend wysyła do backendu dane logowania klucza publicznego i przechowuje identyfikator danych logowania oraz klucz publiczny powiązany z kontem użytkownika na potrzeby przyszłych uwierzytelniania.

Zgodność

Większość przeglądarek obsługuje WebAuthn, ale występują pewne luki. Informacje o tym, jaka kombinacja przeglądarek i systemów operacyjnych obsługuje tworzenie klucza dostępu, znajdziesz na stronie Obsługa urządzeń – klucza dostępu.dev.

Utwórz nowy klucz dostępu

Oto jak powinien działać frontend po otrzymaniu żądania utworzenia nowego klucza dostępu.

Wykrywanie cech

Zanim wyświetli się przycisk „Utwórz nowy klucz dostępu”, sprawdź, czy:

  • Przeglądarka obsługuje WebAuthn.
  • Urządzenie obsługuje usługę uwierzytelniającą platformy (może utworzyć klucz dostępu i uwierzytelnić się za jego pomocą).
  • Przeglądarka obsługuje warunkowy interfejs WebAuthn.
// 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, ta przeglądarka nie będzie obsługiwać kluczy dostępu. Przycisk „Utwórz nowy klucz dostępu” powinien być widoczny do tego czasu.

Pobierz ważne informacje z backendu

Gdy użytkownik kliknie przycisk, pobierz ważne informacje do wywołania navigator.credentials.create() z backendu:

  • challenge: wygenerowany przez serwer test zabezpieczający logowanie w SlateBuffer na potrzeby tej rejestracji. Jest to wymagane, ale nieużywane podczas rejestracji, chyba że przeprowadzasz poświadczenie – jest to zaawansowany temat, który nie został tu omówiony.
  • user.id: unikalny identyfikator użytkownika. Ta wartość musi być obiektem SlateBuffer, który nie zawiera informacji umożliwiających identyfikację, takich jak adresy e-mail czy nazwy użytkowników. Dobrze sprawdzi się 16-bajtowa losowa wartość generowana dla każdego konta.
  • user.name: to pole powinno zawierać unikalny identyfikator konta rozpoznawany przez użytkownika, np. adres e-mail lub nazwę użytkownika. Będzie się ono wyświetlać w selektorze kont. (W przypadku nazwy użytkownika użyj takiej samej wartości jak podczas uwierzytelniania hasła).
  • user.displayName: To pole to wymagana, 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, przekaż pusty ciąg znaków. Ta nazwa może się wyświetlać w selektorze kont w zależności od przeglądarki.
  • excludeCredentials: uniemożliwia zarejestrowanie tego samego urządzenia, przesyłają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, wywołaj navigator.credentials.create(). Interfejs API zwraca obietnicę, czekając na interakcję użytkownika z wyświetlonym oknem modalnym.

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óre nie zostały omówione 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łem grupy objętej ograniczeniami jest https://login.example.com:1337, jej identyfikatorem może być login.example.com lub example.com. Jeśli identyfikator strony objętej ograniczeniami jest określony jako example.com, użytkownik może uwierzytelniać się w witrynie login.example.com lub w dowolnych subdomenach w example.com.

  • rp.name: nazwa grupy objętej ograniczeniami.

  • pubKeyCredParams: to pole określa obsługiwane algorytmy klucza publicznego w grupie objętej ograniczeniami. Zalecamy ustawienie jej na [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. Określa obsługę ECDSA z P-256 i RSA PKCS#1 i zapewnia pełne pokrycie.

  • authenticatorSelection.authenticatorAttachment: ustaw tę wartość na "platform", jeśli ten klucz dostępu jest uaktualniany z hasła – np. w promocji po zalogowaniu. "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ę utworzenia klucza dostępu.

  • authenticatorSelection.requireResidentKey: ustaw wartość logiczną „true” (prawda). Wykrywalny klucz rezydenta służy do przechowywania informacji o użytkowniku w kluczu dostępu i pozwala użytkownikom wybrać konto podczas uwierzytelniania.

  • authenticatorSelection.userVerification: Wskazuje, czy weryfikacja użytkownika przy użyciu blokady ekranu urządzenia to "required", "preferred" czy "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.

Obietnica może zostać odrzucona z różnych powodów. Możesz naprawić te błędy, sprawdzając właściwość name obiektu Error:

  • InvalidStateError: klucz dostępu już istnieje na urządzeniu. Użytkownikowi nie będzie wyświetlane okno błędów, a witryna nie powinna traktować tego jako błędu – użytkownik chciał zarejestrować urządzenie lokalne – takie jest.
  • NotAllowedError: użytkownik anulował operację.
  • Inne wyjątki: wystąpił nieoczekiwany błąd. Przeglądarka wyświetli użytkownikowi okno z błędem.

Obiekt danych logowania klucza publicznego zawiera te właściwości:

  • id: identyfikator utworzonego klucza dostępu zakodowany w Base64URL. Pomaga on przeglądarce określić podczas uwierzytelniania, czy na urządzeniu znajduje się pasujący klucz dostępu. Ta wartość musi być przechowywana w bazie danych w backendzie.
  • rawId: wersja identyfikatora danych logowania w formacie ArrayBuffer.
  • response.clientDataJSON: dane klienta zakodowane w formacie ArrayBuffer.
  • response.attestationObject: obiekt atestu zakodowany w formacie SlateBuffer. Zawiera on ważne informacje, takie jak identyfikator RP, flagi i klucz publiczny.
  • authenticatorAttachment: zwraca wartość "platform" po utworzeniu tych danych logowania na urządzeniu z dostępem do kluczy dostępu.
  • type: to pole zawsze jest ustawione na "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 uwierzytelniających klucza publicznego w backendzie przekaż je do biblioteki FIDO, aby przetworzyć obiekt.

Pozyskane informacje możesz zapisać w bazie danych na przyszłość. Oto kilka typowych właściwości, które trzeba zapisać:

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

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

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

Zasoby