1. Zanim zaczniesz
Korzystanie z kluczy dostępu zamiast haseł to świetny sposób na zwiększenie bezpieczeństwa kont użytkowników w witrynach, a także na uproszczenie i ułatwienie ich obsługi. Umożliwiają logowanie się w witrynie lub aplikacji za pomocą funkcji blokady ekranu urządzenia, np. odcisku palca, skanu twarzy lub kodu PIN. Zanim użytkownik będzie mógł zalogować się za pomocą klucza dostępu, musi go utworzyć, powiązać z kontem użytkownika i zapisać jego klucz publiczny na serwerze.
W tym ćwiczeniu przekształcisz podstawowe logowanie za pomocą formularza z nazwą użytkownika i hasłem w logowanie obsługujące klucze dostępu, które obejmuje:
- Przycisk, który tworzy klucz dostępu po zalogowaniu się użytkownika.
- Interfejs z listą zarejestrowanych kluczy dostępu.
- Istniejący formularz logowania, który umożliwia użytkownikom logowanie się za pomocą zarejestrowanego klucza dostępu dzięki autouzupełnianiu formularza.
Wymagania wstępne
- Podstawowa znajomość JavaScriptu
- Podstawowa wiedza na temat interfejsu Web Authentication API (WebAuthn)
Czego się nauczysz
- Jak utworzyć klucz dostępu
- Jak uwierzytelniać użytkowników za pomocą klucza dostępu.
- Jak umożliwić formularzowi sugerowanie klucza dostępu jako opcji logowania.
2. Konfiguracja
W tym ćwiczeniu sklonujesz z GitHuba niekompletną aplikację w wersji demonstracyjnej, a następnie dokończysz implementację obsługi kluczy dostępu.
Klonowanie projektu
- Otwórz projekt na GitHubie.
- Sklonuj lub pobierz projekt.

Uruchamianie projektu
- Otwórz terminal i użyj polecenia
cd start, aby zmienić katalog. - Aby zainstalować zależności projektu, uruchom
npm install. - Skompiluj i uruchom projekt za pomocą polecenia
npm run build && IS_LOCAL=1 npm run start. - Otwórz w przeglądarce adres http://localhost:8080/.
Sprawdź stan początkowy witryny
- Na stronie wpisz losową nazwę użytkownika i kliknij Dalej.
- Wpisz losowe hasło, a potem kliknij Zaloguj się. Hasło jest ignorowane, ale nadal jesteś uwierzytelniony(-a) i przekierowywany(-a) na stronę główną.
- Jeśli chcesz zmienić wyświetlaną nazwę, zrób to. To wszystko, co możesz zrobić w stanie początkowym.
- Kliknij Wyloguj się.
W tym stanie użytkownicy muszą wpisywać hasło przy każdym logowaniu. Dodaj do tego formularza obsługę kluczy dostępu, aby użytkownicy mogli logować się za pomocą funkcji blokady ekranu urządzenia.
Więcej informacji o działaniu kluczy dostępu znajdziesz w artykule Jak działają klucze dostępu?.
3. Dodawanie możliwości utworzenia klucza dostępu
Aby umożliwić użytkownikom uwierzytelnianie za pomocą klucza dostępu, musisz dać im możliwość utworzenia i zarejestrowania klucza dostępu oraz przechowywania jego klucza publicznego na serwerze.

Chcesz zezwolić na tworzenie klucza dostępu po zalogowaniu się użytkownika za pomocą hasła i dodać interfejs, który umożliwi użytkownikom tworzenie klucza dostępu i wyświetlanie listy wszystkich zarejestrowanych kluczy dostępu na stronie /home. W następnej sekcji utworzysz funkcję, która tworzy i rejestruje klucz dostępu.
Tworzenie funkcji registerCredential()
- W wybranym edytorze kodu otwórz katalog
start. - Otwórz plik
public/client.jsi przewiń go na koniec. - Po odpowiednim komentarzu dodaj tę funkcję
registerCredential():
public/client.js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to create a passkey: Create a credential.
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
};
Ta funkcja tworzy i rejestruje klucz dostępu na serwerze.
Pobieranie wyzwania i innych opcji z punktu końcowego serwera
Zanim utworzysz klucz dostępu, musisz poprosić serwer o przekazanie parametrów do WebAuthn, w tym wyzwania. WebAuthn to interfejs API przeglądarki, który umożliwia użytkownikowi utworzenie klucza dostępu i uwierzytelnienie go za pomocą tego klucza. Na szczęście w tym laboratorium masz już punkt końcowy serwera, który odpowiada takimi parametrami.
- Aby uzyskać wyzwanie i inne opcje z punktu końcowego serwera, dodaj ten kod do treści funkcji
registerCredential()po odpowiednim komentarzu:
public/client.js
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const _options = await _fetch('/auth/registerRequest');
Poniższy fragment kodu zawiera przykładowe opcje, które otrzymujesz z serwera:
{
challenge: *****,
rp: {
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', 'hybrid'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
Protokół między serwerem a klientem nie jest częścią specyfikacji WebAuthn. Serwer w tym laboratorium kodu został jednak zaprojektowany tak, aby zwracać plik JSON jak najbardziej podobny do słownika PublicKeyCredentialCreationOptions, który jest przekazywany do interfejsu WebAuthn API navigator.credentials.create().
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w słowniku PublicKeyCredentialCreationOptions:
Parametry | Teksty reklam |
Wygenerowane przez serwer wyzwanie w obiekcie | |
Unikalny identyfikator użytkownika. Ta wartość musi być obiektem | |
To pole powinno zawierać unikalny identyfikator konta rozpoznawalny przez użytkownika, np. jego adres e-mail lub nazwę użytkownika. Jest on widoczny w selektorze kont. (Jeśli używasz nazwy użytkownika, użyj tej samej wartości co w przypadku uwierzytelniania za pomocą hasła). | |
To pole zawiera opcjonalną, przyjazną dla użytkownika nazwę konta. Nie musi być niepowtarzalna i może być wybraną przez użytkownika nazwą. Jeśli w witrynie nie ma odpowiedniej wartości, którą można tu umieścić, przekaż pusty ciąg znaków. W zależności od przeglądarki może się ona wyświetlać w selektorze kont. | |
Identyfikator strony ufającej (RP) to domena. Witryna może określić swoją domenę lub 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 mieć postać | |
To pole określa obsługiwane przez RP algorytmy klucza publicznego. Zalecamy ustawienie tej wartości na | |
Zawiera listę zarejestrowanych identyfikatorów danych logowania, aby zapobiec ponownej rejestracji tego samego urządzenia. Jeśli jest podany, element | |
Ustaw wartość | |
Ustaw wartość logiczną | |
Ustaw wartość |
Tworzenie danych logowania
- W treści funkcji
registerCredential()po odpowiednim komentarzu przekonwertuj niektóre parametry zakodowane w formacie Base64URL z powrotem na postać binarną, a mianowicie ciągi tekstoweuser.idichallengeoraz wystąpienia ciągu tekstowegoidzawarte w tablicyexcludeCredentials. Możesz to zrobić za pomocą funkcjiPublicKeyCredential.parseCreationOptionsFromJSON():
public/client.js
// TODO: Add an ability to create a passkey: Create a credential.
// Deserialize and decode the `PublicKeyCredential.parseCreationOptionsFromJSON()`.
const options = PublicKeyCredential.parseCreationOptionsFromJSON(_options);
- W następnym wierszu ustaw
authenticatorSelection.authenticatorAttachmentna"platform", aauthenticatorSelection.requireResidentKeynatrue. Umożliwia to korzystanie tylko z uwierzytelniacza platformy (samego urządzenia) z możliwością wykrywalnych danych logowania.
public/client.js
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
- W następnym wierszu wywołaj metodę
navigator.credentials.create(), aby utworzyć dane logowania.
public/client.js
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
W ramach tego wywołania przeglądarka próbuje zweryfikować tożsamość użytkownika za pomocą blokady ekranu urządzenia.
Zarejestruj dane logowania w punkcie końcowym serwera.
Po zweryfikowaniu tożsamości użytkownika tworzony i zapisywany jest klucz dostępu. Witryna otrzymuje obiekt danych logowania, który zawiera klucz publiczny, który możesz wysłać na serwer, aby zarejestrować klucz dostępu.
Ten fragment kodu zawiera przykładowy obiekt danych logowania:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"attestationObject": *****,
"transports": ["internal", "hybrid"]
},
"authenticatorAttachment": "platform"
}
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w obiekcie PublicKeyCredential:
Parametry | Teksty reklam |
Identyfikator utworzonego klucza dostępu zakodowany w formacie Base64URL. Ten identyfikator pomaga przeglądarce określić, czy podczas uwierzytelniania na urządzeniu znajduje się pasujący klucz dostępu. Ta wartość musi być przechowywana w bazie danych na backendzie. | |
| |
Obiekt | |
Zakodowany obiekt atestu | |
Lista transportów obsługiwanych przez urządzenie: | |
Zwraca wartość |
Aby wysłać obiekt danych logowania na serwer, wykonaj te czynności:
- Zakoduj parametry binarne danych logowania w formacie Base64URL, aby można je było przekazać na serwer jako ciąg znaków. Aby to zrobić, możesz użyć
.toJSON():
public/client.js
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
// Encode and serialize the `PublicKeyCredential`.
const credential = JSON.stringify(cred);
- W następnej linii wyślij obiekt na serwer:
public/client.js
return await _fetch('/auth/registerResponse', credential);
Gdy uruchomisz program, serwer zwróci wartość HTTP code 200, co oznacza, że dane logowania są zarejestrowane.
Masz już kompletną funkcję registerCredential().
Sprawdź kod rozwiązania w tej sekcji
public/client.js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const _options = await _fetch('/auth/registerRequest');
// TODO: Add an ability to create a passkey: Create a credential.
// Deserialize and decode the `PublicKeyCredential.parseCreationOptionsFromJSON()`.
const options = PublicKeyCredential.parseCreationOptionsFromJSON(_options);
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
// Encode and serialize the `PublicKeyCredential`.
const credential = JSON.stringify(cred);
return await _fetch('/auth/registerResponse', credential);
};
4. Tworzenie interfejsu do rejestrowania danych logowania za pomocą klucza dostępu i zarządzania nimi
Teraz, gdy funkcja registerCredential() jest dostępna, potrzebujesz przycisku, aby ją wywołać. Musisz też wyświetlać listę zarejestrowanych kluczy dostępu.

Dodawanie kodu HTML obiektu zastępczego
- W edytorze otwórz plik
views/home.html. - Po odpowiednim komentarzu dodaj element interfejsu, który wyświetla przycisk rejestracji klucza dostępu i listę kluczy dostępu:
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3>Your registered passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mdui-button id="create-passkey" class="hidden" icon="fingerprint" type="button">Create a passkey</mdui-button>
Element div#list jest elementem zastępczym listy.
Sprawdzanie obsługi kluczy dostępu
Aby wyświetlać opcję tworzenia klucza dostępu tylko użytkownikom, których urządzenia obsługują klucze dostępu, musisz najpierw sprawdzić, czy WebAuthn jest dostępny. Jeśli tak, musisz usunąć klasę hidden, aby wyświetlić przycisk Utwórz klucz dostępu.
Aby sprawdzić, czy środowisko obsługuje klucze dostępu, wykonaj te czynności:
- Na końcu pliku
views/home.html, po odpowiednim komentarzu, napisz warunek, który zostanie wykonany, jeśli zmiennewindow.PublicKeyCredential,PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailableiPublicKeyCredential.isConditionalMediationAvailablemają wartośćtrue.
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
- W treści warunku sprawdź, czy urządzenie może utworzyć klucz dostępu, a następnie sprawdź, czy klucz dostępu można zasugerować w ramach automatycznego wypełniania formularza.
views/home.html
try {
const capabilities = await PublicKeyCredential.getClientCapabilities();
// Is conditional UI available in this browser?
if (capabilities.conditionalGet === true &&
capabilities.passkeyPlatformAuthenticator === true) {
- Jeśli wszystkie warunki są spełnione, wyświetl przycisk tworzenia klucza dostępu. W przeciwnym razie wyświetl komunikat ostrzegawczy.
views/home.html
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
Wyświetlanie zarejestrowanych kluczy dostępu na liście
- Zdefiniuj funkcję
renderCredentials(), która pobiera zarejestrowane klucze dostępu z serwera i wyświetla je na liście. Na szczęście masz już/auth/getKeyspunkt końcowy serwera, który umożliwia pobieranie zarejestrowanych kluczy dostępu zalogowanego użytkownika.
views/home.html
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = res.length > 0 ? html`
<mdui-list>
${res.map(cred => html`
<mdui-list-item>
${cred.name || 'Unnamed'}
<mdui-button-icon data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed'}" @click="${rename}" icon="edit" slot="end-icon"></mdui-button-icon>
<mdui-button-icon data-cred-id="${cred.id}" @click="${remove}" icon="delete" slot="end-icon"></mdui-button-icon>
</mdui-list-item>`)}
</mdui-list>` : html`
<mdui-list>
<mdui-list-item>No credentials found.</mdui-list-item>
</mdui-list>`;
render(creds, list);
};
- W następnym wierszu wywołaj funkcję
renderCredentials(), aby wyświetlić zarejestrowane klucze dostępu, gdy tylko użytkownik wejdzie na stronę/homew ramach inicjowania.
views/home.html
renderCredentials();
Tworzenie i rejestrowanie klucza dostępu
Aby utworzyć i zarejestrować klucz dostępu, musisz wywołać zaimplementowaną wcześniej funkcję registerCredential().
Aby wywołać funkcję registerCredential() po kliknięciu przycisku Utwórz klucz dostępu, wykonaj te czynności:
- W pliku po zastępczym kodzie HTML znajdź następującą instrukcję
import:
views/home.html
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
} from '/client.js';
- Na końcu treści instrukcji
importdodaj funkcjęregisterCredential().
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
- Na końcu pliku po odpowiednim komentarzu zdefiniuj funkcję
register(), która wywołuje funkcjęregisterCredential()i interfejs ładowania oraz wywołuje funkcjęrenderCredentials()po rejestracji. Wyjaśnia to, że przeglądarka tworzy klucz dostępu i wyświetla komunikat o błędzie, gdy coś pójdzie nie tak.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
- W treści funkcji
register()przechwyć wyjątki. Metodanavigator.credentials.create()zgłasza błądInvalidStateError, gdy na urządzeniu istnieje już klucz dostępu. Jest to badane za pomocą tablicyexcludeCredentials. W takim przypadku wyświetlasz użytkownikowi odpowiedni komunikat. W przypadku anulowania okna uwierzytelniania przez użytkownika zwraca też błądNotAllowedError. W takim przypadku zignoruj go.
views/home.html
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
- W wierszu po funkcji
register()dołącz funkcjęregister()do zdarzeniaclickdla przycisku Utwórz klucz dostępu.
views/home.html
createPasskey.addEventListener('click', register);
Sprawdź kod rozwiązania w tej sekcji
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3>Your registered passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mdui-button id="create-passkey" icon="fingerprint" type="button">Create a passkey</mdui-button>
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Is WebAuthn available in this browser?
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
const capabilities = await PublicKeyCredential.getClientCapabilities();
// Is conditional UI available in this browser?
if (capabilities.conditionalGet === true &&
capabilities.passkeyPlatformAuthenticator === true) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mdui-list>
${res.map(cred => html`
<mdui-list-item>
${cred.name || 'Unnamed'}
<mdui-button-icon data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed'}" @click="${rename}" icon="edit" slot="end-icon"></mdui-button-icon>
<mdui-button-icon data-cred-id="${cred.id}" @click="${remove}" icon="delete" slot="end-icon"></mdui-button-icon>
</mdui-list-item>`)}
</mdui-list>` : html`
<mdui-list>
<mdui-list-item>No credentials found.</mdui-list-item>
</mdui-list>`}`;
render(creds, list);
};
renderCredentials();
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
createPasskey.addEventListener('click', register);
Wypróbuj
Jeśli wykonasz wszystkie opisane dotychczas czynności, wdrożysz w witrynie możliwość tworzenia, rejestrowania i wyświetlania kluczy dostępu.
Aby wypróbować tę funkcję, wykonaj te czynności:
- Zaloguj się w witrynie, używając losowej nazwy użytkownika i hasła.
- Kliknij Utwórz klucz dostępu.
- Potwierdź swoją tożsamość za pomocą blokady ekranu urządzenia.
- Sprawdź, czy klucz dostępu jest zarejestrowany i wyświetla się w sekcji Twoje zarejestrowane klucze dostępu na stronie internetowej.

Zmienianie nazwy i usuwanie zarejestrowanych kluczy dostępu
Powinna być możliwość zmiany nazwy zarejestrowanych kluczy dostępu lub ich usunięcia z listy. Możesz sprawdzić, jak to działa w kodzie, ponieważ jest on dostępny w ramach codelabu.
W Chrome możesz usunąć zarejestrowane klucze dostępu ze strony chrome://settings/passkeys na komputerze lub z menedżera haseł w ustawieniach na urządzeniu z Androidem.
Informacje o tym, jak zmieniać nazwy zarejestrowanych kluczy dostępu i usuwać je na innych platformach, znajdziesz na odpowiednich stronach pomocy tych platform.
5. Dodanie możliwości uwierzytelniania za pomocą klucza dostępu
Użytkownicy mogą teraz tworzyć i rejestrować klucze dostępu, a potem bezpiecznie używać ich do uwierzytelniania w Twojej witrynie. Teraz musisz dodać do swojej witryny możliwość uwierzytelniania za pomocą klucza dostępu.
Tworzenie funkcji authenticate()
- W pliku
public/client.jspo odpowiednim komentarzu utwórz funkcję o nazwieauthenticate(), która lokalnie weryfikuje użytkownika, a następnie na serwerze:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
};
Pobieranie wyzwania i innych opcji z punktu końcowego serwera
Zanim poprosisz użytkownika o uwierzytelnienie, musisz poprosić serwer o przekazanie parametrów do WebAuthn, w tym wyzwania.
- W treści funkcji
authenticate()po odpowiednim komentarzu wywołaj funkcję_fetch(), aby wysłać żądaniePOSTdo serwera:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
// Base64URL decode the challenge.
const options = PublicKeyCredential.parseRequestOptionsFromJSON(_options);
Serwer w tym laboratorium kodu został zaprojektowany tak, aby zwracać JSON, który jest jak najbardziej podobny do słownika PublicKeyCredentialRequestOptions przekazywanego do interfejsu WebAuthn navigator.credentials.get() API. Poniższy fragment kodu zawiera przykładowe opcje, które powinny zostać zwrócone:
{
"challenge": *****,
"rpId": "localhost",
"allowCredentials": []
}
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w słowniku PublicKeyCredentialRequestOptions:
Parametry | Teksty reklam |
Wygenerowane przez serwer wyzwanie w obiekcie | |
Identyfikator RP to domena. Witryna może określić swoją domenę lub sufiks, który można zarejestrować. Ta wartość musi być zgodna z parametrem | |
Ta właściwość służy do znajdowania uwierzytelniaczy kwalifikujących się do tego uwierzytelniania. Przekaż pustą tablicę lub pozostaw ją nieokreśloną, aby przeglądarka wyświetliła selektor kont. Dowiedz się więcej o tym, jak zachowują się | |
Ustaw wartość |
Lokalna weryfikacja użytkownika i uzyskanie danych logowania
- W treści funkcji
authenticate()po odpowiednim komentarzu przekonwertuj parametrchallengez powrotem na postać binarną:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
- Aby otworzyć selektor konta, gdy użytkownik się uwierzytelni, przekaż pustą tablicę do parametru
allowCredentials:
public/client.js
// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];
Selektor konta korzysta z informacji o użytkowniku przechowywanych w kluczu dostępu.
- Wywołaj metodę
navigator.credentials.get()wraz z opcjąmediation: 'conditional':
public/client.js
// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
Ta opcja nakazuje przeglądarce warunkowe sugerowanie kluczy dostępu w ramach automatycznego wypełniania formularzy.
Weryfikacja danych logowania
Gdy użytkownik potwierdzi swoją tożsamość lokalnie, otrzymasz obiekt danych logowania zawierający podpis, który możesz zweryfikować na serwerze.
Ten fragment kodu zawiera przykładowy obiekt PublicKeyCredential:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"authenticatorData": *****,
"signature": *****,
"userHandle": *****
},
authenticatorAttachment: "platform"
}
Poniższa tabela nie jest wyczerpująca, ale zawiera ważne parametry w obiekcie PublicKeyCredential:
Parametry | Teksty reklam |
Identyfikator uwierzytelnionych danych logowania klucza dostępu zakodowany w formacie Base64URL. | |
| |
Obiekt | |
Obiekt | |
Obiekt | |
Obiekt | |
Zwraca ciąg znaków |
Aby wysłać obiekt danych logowania na serwer, wykonaj te czynności:
- W treści funkcji
authenticate()po odpowiednim komentarzu zakoduj parametry binarne danych logowania, aby można je było przesłać na serwer jako ciąg tekstowy. Aby to zrobić, możesz użyć.toJSON():
public/client.js
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
// Encode and serialize the `PublicKeyCredential`.
const credential = JSON.stringify(cred);
- Wyślij obiekt na serwer:
public/client.js
return await _fetch(`/auth/signinResponse`, credential);
Po uruchomieniu programu serwer zwraca wartość HTTP code 200, co oznacza, że dane logowania zostały zweryfikowane.
Masz teraz pełną funkcję authentication().
Sprawdź kod rozwiązania w tej sekcji
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the
challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
// TODO: Add an ability to authenticate with a passkey: Locally verify
the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
// The empty allowCredentials array invokes an account selector
by discoverable credentials.
options.allowCredentials = [];
// Invoke the WebAuthn get() function.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData =
base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
return await _fetch(`/auth/signinResponse`, credential);
};
6. Dodawanie kluczy dostępu do autouzupełniania w przeglądarce
Gdy użytkownik wróci, chcesz, aby mógł zalogować się jak najłatwiej i najbezpieczniej. Jeśli dodasz do strony logowania przycisk Zaloguj się za pomocą klucza dostępu, użytkownik może go nacisnąć, wybrać klucz dostępu w selektorze kont w przeglądarce i użyć blokady ekranu, aby potwierdzić tożsamość.
Przejście z hasła na klucz dostępu nie następuje jednak u wszystkich użytkowników jednocześnie. Oznacza to, że nie możesz pozbyć się haseł, dopóki wszyscy użytkownicy nie przejdą na klucze dostępu, więc do tego czasu musisz pozostawić formularz logowania oparty na hasłach. Jeśli jednak pozostawisz formularz hasła i przycisk klucza dostępu, użytkownicy będą musieli dokonać niepotrzebnego wyboru, którego z nich użyć do zalogowania się. Najlepiej, aby proces logowania był prosty.
W takiej sytuacji przydaje się interfejs warunkowy. Warunkowy interfejs to funkcja WebAuthn, która umożliwia wyświetlanie pola do wprowadzania danych formularza, aby sugerować klucz dostępu jako część elementów autouzupełniania oprócz haseł. Jeśli użytkownik kliknie klucz dostępu w sugestiach autouzupełniania, pojawi się prośba o użycie blokady ekranu urządzenia w celu lokalnego potwierdzenia tożsamości. Zapewnia to płynne logowanie, ponieważ działanie użytkownika jest niemal identyczne jak w przypadku logowania za pomocą hasła.

Włączanie interfejsu warunkowego
Aby włączyć interfejs warunkowy, wystarczy dodać token webauthn do atrybutu autocomplete pola do wprowadzania danych. Po ustawieniu tokena możesz wywołać metodę navigator.credentials.get() za pomocą ciągu znaków mediation: 'conditional', aby warunkowo wywołać interfejs blokady ekranu.
- Aby włączyć interfejs warunkowy, zastąp istniejące pola wprowadzania nazwy użytkownika tym kodem HTML po odpowiednim komentarzu w pliku
view/index.html:
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<mdui-text-field id="username" label="Username" name="username" autocomplete="username webauthn" autofocus></mdui-text-field>
Wykrywanie funkcji, wywoływanie WebAuthn i włączanie interfejsu warunkowego
- W pliku
view/index.htmlpo odpowiednim komentarzu zastąp istniejącą instrukcjęimporttym kodem:
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from "/client.js";
Ten kod importuje funkcję authenticate(), którą zaimplementowano wcześniej.
- Sprawdź, czy obiekt
window.PulicKeyCredentialjest dostępny i czy metodaPublicKeyCredential.isConditionalMediationAvailable()zwraca wartośćtrue, a następnie wywołaj funkcjęauthenticate():
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (window.PublicKeyCredential &&
PublicKeyCredential.getClientCapabilities) {
try {
// Is conditional UI available in this browser?
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet) {
// If conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$("#username").value = user.username;
loading.start();
location.href = "/home";
} else {
throw new Error("User not found.");
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== "NotAllowedError") {
console.error(e);
alert(e.message);
}
}
}
Sprawdź kod rozwiązania w tej sekcji
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<mdui-text-field id="username" label="Username" name="username" autocomplete="username webauthn" autofocus></mdui-text-field>
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from '/client.js';
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
// Is WebAuthn available on this browser?
if (window.PublicKeyCredential &&
PublicKeyCredential.getClientCapabilities) {
try {
// Is conditional UI available in this browser?
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet) {
// If conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$('#username').value = user.username;
loading.start();
location.href = '/home';
} else {
throw new Error('User not found.');
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== 'NotAllowedError') {
console.error(e);
alert(e.message);
}
}
}
Wypróbuj
Wdrożono tworzenie, rejestrowanie, wyświetlanie i uwierzytelnianie kluczy dostępu w witrynie.
Aby wypróbować tę funkcję, wykonaj te czynności:
- Otwórz kartę podglądu.
- W razie potrzeby wyloguj się.
- Kliknij pole tekstowe nazwy użytkownika. Pojawi się okno.
- Wybierz konto, na które chcesz się zalogować.
- Potwierdź swoją tożsamość za pomocą blokady ekranu urządzenia. Nastąpi przekierowanie na stronę
/homei zalogujesz się.

7. Gratulacje!
To ćwiczenie zostało ukończone. Jeśli masz pytania, zadaj je na liście adresowej FIDO-DEV lub na StackOverflow, dodając tag passkey.