Passkeys mit Formularautofill in einer Webanwendung implementieren

1. Hinweis

Die Verwendung von Passkeys anstelle von Passwörtern ist eine hervorragende Möglichkeit für Websites, die Nutzerkonten sicherer, einfacher und benutzerfreundlicher zu gestalten. Mit einem Passkey kann sich ein Nutzer auf einer Website oder in einer App anmelden, indem er die Displaysperre des Geräts verwendet, z. B. einen Fingerabdruck, die Gesichtserkennung oder eine Geräte-PIN. Ein Passkey muss erstellt, einem Nutzerkonto zugewiesen und sein öffentlicher Schlüssel auf einem Server gespeichert werden, bevor sich ein Nutzer damit anmelden kann.

In diesem Codelab wandeln Sie eine einfache formularbasierte Anmeldung mit Nutzername und Passwort in eine Anmeldung um, die Passkeys unterstützt und Folgendes umfasst:

  • Eine Schaltfläche, mit der nach der Anmeldung des Nutzers ein Passkey erstellt wird.
  • Eine Benutzeroberfläche, auf der eine Liste der registrierten Passkeys angezeigt wird.
  • Das vorhandene Anmeldeformular, mit dem sich Nutzer mit einem registrierten Passkey über die Autofill-Funktion anmelden können.

Vorbereitung

Lerninhalte

  • So erstellen Sie einen Passkey.
  • Nutzer mit einem Passkey authentifizieren
  • So kann in einem Formular ein Passkey als Anmeldeoption vorgeschlagen werden.

2. Einrichten

In diesem Codelab klonen Sie eine unvollständige Demo-App von GitHub und schließen dann die Implementierung der Passkey-Unterstützung ab.

Projekt klonen

  1. Öffnen Sie das Projekt auf GitHub.
  2. Klonen Sie das Projekt oder laden Sie es herunter.

ac587c53b746785a.png

Projekt ausführen

  1. Öffnen Sie ein Terminal und verwenden Sie cd start, um das Verzeichnis zu wechseln.
  2. Führen Sie npm install aus, um die Projektabhängigkeiten zu installieren.
  3. Erstellen Sie das Projekt mit npm run build && IS_LOCAL=1 npm run start und führen Sie es aus.
  4. Öffnen Sie in Ihrem Browser http://localhost:8080/.

Ausgangszustand der Website prüfen

  1. Geben Sie auf der Website einen zufälligen Nutzernamen ein und klicken Sie auf Weiter.
  2. Geben Sie ein beliebiges Passwort ein und klicken Sie auf Anmelden. Das Passwort wird ignoriert, Sie werden aber trotzdem authentifiziert und zur Startseite weitergeleitet.
  3. Wenn Sie Ihren Anzeigenamen ändern möchten, können Sie das tun. Das ist alles, was Sie im Ausgangszustand tun können.
  4. Klicken Sie auf Abmelden.

In diesem Status müssen Nutzer bei jeder Anmeldung ein Passwort eingeben. Sie fügen diesem Formular Unterstützung für Passkeys hinzu, damit sich Nutzer mit der Displaysperre des Geräts anmelden können.

Weitere Informationen zur Funktionsweise von Passkeys finden Sie unter Wie funktionieren Passkeys?.

3. Möglichkeit zum Erstellen eines Passkeys hinzufügen

Damit sich Nutzer mit einem Passkey authentifizieren können, müssen Sie ihnen die Möglichkeit geben, einen Passkey zu erstellen und zu registrieren und den öffentlichen Schlüssel auf dem Server zu speichern.

9b84dbaec66afe9c.png

Sie möchten die Erstellung eines Passkeys zulassen, nachdem sich der Nutzer mit einem Passwort angemeldet hat, und eine Benutzeroberfläche hinzufügen, über die Nutzer einen Passkey erstellen und eine Liste aller registrierten Passkeys auf der Seite /home aufrufen können. Im nächsten Abschnitt erstellen Sie eine Funktion, mit der ein Passkey erstellt und registriert wird.

Funktion registerCredential() erstellen

  1. Öffnen Sie das Verzeichnis start in einem Code-Editor Ihrer Wahl.
  2. Rufen Sie die Datei public/client.js auf und scrollen Sie zum Ende.
  3. Fügen Sie nach dem entsprechenden Kommentar die folgende registerCredential()-Funktion hinzu:

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.

};

Diese Funktion erstellt und registriert einen Passkey auf dem Server.

Herausforderung und andere Optionen vom Serverendpunkt abrufen

Bevor ein Passkey erstellt wird, müssen Sie Parameter anfordern, die Sie vom Server an WebAuthn übergeben, einschließlich einer Challenge. WebAuthn ist eine Browser-API, mit der ein Nutzer einen Passkey erstellen und sich mit dem Passkey authentifizieren kann. Glücklicherweise haben Sie in diesem Codelab bereits einen Serverendpunkt, der mit solchen Parametern antwortet.

  • Fügen Sie den folgenden Code nach dem entsprechenden Kommentar in den Textkörper der Funktion registerCredential() ein, um die Challenge und andere Optionen vom Serverendpunkt abzurufen:

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');

Das folgende Code-Snippet enthält Beispieloptionen, die Sie vom Server erhalten:

{
  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,
  }
}

Das Protokoll zwischen einem Server und einem Client ist nicht Teil der WebAuthn-Spezifikation. Der Server dieses Codelabs ist jedoch so konzipiert, dass er ein JSON zurückgibt, das dem PublicKeyCredentialCreationOptions-Dictionary, das an die WebAuthn-navigator.credentials.create()-API übergeben wird, so ähnlich wie möglich ist.

Die folgende Tabelle ist nicht vollständig, enthält aber die wichtigen Parameter im PublicKeyCredentialCreationOptions-Dictionary:

Parameter

Textzeilen

challenge

Eine vom Server generierte Challenge in einem ArrayBuffer-Objekt für diese Registrierung. Dies ist für die Registrierung erforderlich, wird aber nicht verwendet, es sei denn, Sie führen eine Bestätigung durch. Dies ist ein fortgeschrittenes Thema, das in diesem Codelab nicht behandelt wird.

user.id

Die eindeutige ID eines Nutzers. Dieser Wert muss ein ArrayBuffer-Objekt sein, das keine personenbezogenen Daten wie E-Mail-Adressen oder Nutzernamen enthält. Ein zufälliger 16-Byte-Wert, der pro Konto generiert wird, ist eine gute Lösung.

user.name

Dieses Feld sollte eine eindeutige Kennung für das Konto enthalten, die für den Nutzer erkennbar ist, z. B. seine E-Mail-Adresse oder seinen Nutzernamen. Sie wird in der Kontoauswahl angezeigt. Wenn Sie einen Nutzernamen verwenden, verwenden Sie denselben Wert wie bei der Passwortauthentifizierung.

user.displayName

Dieses Feld ist ein optionaler, nutzerfreundlicher Name für das Konto. Er muss nicht eindeutig sein und kann der vom Nutzer gewählte Name sein. Wenn Ihre Website keinen geeigneten Wert für dieses Feld hat, übergeben Sie einen leeren String. Je nach Browser wird dies möglicherweise in der Kontoauswahl angezeigt.

rp.id

Eine Relying Party-ID (RP) ist eine Domain. Eine Website kann entweder ihre Domain oder ein registrierbares Suffix angeben. Wenn der Ursprung eines RP beispielsweise https://login.beispiel.de:1337 ist, kann die RP-ID entweder login.example.com oder example.com sein. Wenn die RP-ID als example.com angegeben ist, kann sich der Nutzer auf login.beispiel.de oder einer anderen Subdomain von beispiel.de authentifizieren.

pubKeyCredParams

In diesem Feld werden die vom RP unterstützten Algorithmen für öffentliche Schlüssel angegeben. Wir empfehlen, den Wert auf [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}] zu setzen. Dies gibt die Unterstützung für ECDSA mit P-256 und RSA PKCS#1 an. Durch die Unterstützung dieser beiden wird eine vollständige Abdeckung erreicht.

excludeCredentials

Stellt eine Liste der bereits registrierten Anmeldedaten-IDs bereit, um eine doppelte Registrierung desselben Geräts zu verhindern. Falls angegeben, sollte das transports-Element das Ergebnis des Aufrufs der getTransports()-Funktion während der Registrierung der einzelnen Anmeldedaten enthalten. Weitere Informationen finden Sie in unserer Dokumentation zum Verhindern der Erstellung eines neuen Passkeys, wenn bereits einer vorhanden ist.

authenticatorSelection.authenticatorAttachment

Legen Sie einen "platform"-Wert fest. Das bedeutet, dass Sie einen Authenticator möchten, der in das Plattformgerät eingebettet ist, sodass der Nutzer nicht aufgefordert wird, z. B. einen USB-Sicherheitsschlüssel einzustecken.

authenticatorSelection.requireResidentKey

Auf einen booleschen true-Wert festgelegt. Ein auffindbarer Anmeldedatensatz (residenter Schlüssel) kann verwendet werden, ohne dass der Server die ID des Anmeldedatensatzes bereitstellen muss. Er ist also mit der automatischen Vervollständigung kompatibel. Weitere Informationen

authenticatorSelection.userVerification

Legen Sie den Wert auf "preferred" fest oder lassen Sie ihn weg, da dies der Standardwert ist. Gibt an, ob eine Nutzerbestätigung, bei der die Displaysperre des Geräts verwendet wird, "required", "preferred" oder "discouraged" ist. Wenn Sie einen "preferred"-Wert festlegen, wird eine Nutzerbestätigung angefordert, wenn das Gerät dies unterstützt. Weitere Informationen

Anmeldedaten erstellen

  1. Konvertieren Sie im Hauptteil der Funktion registerCredential() nach dem entsprechenden Kommentar einige mit Base64URL codierte Parameter zurück in Binärdaten, insbesondere die Strings user.id und challenge sowie Instanzen des Strings id, die im Array excludeCredentials enthalten sind. Dazu können Sie die Funktion PublicKeyCredential.parseCreationOptionsFromJSON() verwenden:

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);
  1. Legen Sie in der nächsten Zeile authenticatorSelection.authenticatorAttachment auf "platform" und authenticatorSelection.requireResidentKey auf true fest. Dadurch kann nur ein Plattform-Authenticator (das Gerät selbst) mit einer Funktion für erkennbare Anmeldedaten verwendet werden.

public/client.js

// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
  authenticatorAttachment: 'platform',
  requireResidentKey: true
}
  1. Rufen Sie in der nächsten Zeile die Methode navigator.credentials.create() auf, um Anmeldedaten zu erstellen.

public/client.js

// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
  publicKey: options,
});

Mit diesem Aufruf versucht der Browser, die Identität des Nutzers mit der Displaysperre des Geräts zu bestätigen.

Anmeldedaten beim Serverendpunkt registrieren

Nachdem der Nutzer seine Identität bestätigt hat, wird ein Passkey erstellt und gespeichert. Die Website erhält ein Anmeldedatenobjekt, das einen öffentlichen Schlüssel enthält, den Sie an den Server senden können, um den Passkey zu registrieren.

Das folgende Code-Snippet enthält ein Beispiel für ein Anmeldedatenobjekt:

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "attestationObject": *****,
    "transports": ["internal", "hybrid"]
  },
  "authenticatorAttachment": "platform"
}

Die folgende Tabelle ist nicht vollständig, enthält aber die wichtigen Parameter im PublicKeyCredential-Objekt:

Parameter

Textzeilen

id

Eine Base64URL-codierte ID des erstellten Passkeys. Anhand dieser ID kann der Browser bei der Authentifizierung feststellen, ob sich auf dem Gerät ein passender Passkey befindet. Dieser Wert muss in der Datenbank im Backend gespeichert werden.

rawId

Eine ArrayBuffer-Objektversion der Berechtigungsnachweis-ID.

response.clientDataJSON

Ein ArrayBuffer-Objekt mit codierten Clientdaten.

response.attestationObject

Ein ArrayBuffer-codiertes Attestierungsobjekt. Es enthält wichtige Informationen wie eine RP‑ID, Flags und einen öffentlichen Schlüssel.

response.transports

Eine Liste der vom Gerät unterstützten Transportmethoden: "internal" bedeutet, dass das Gerät einen Passkey unterstützt. "hybrid" bedeutet, dass auch die Authentifizierung auf einem anderen Gerät unterstützt wird.

authenticatorAttachment

Gibt "platform" zurück, wenn dieser Berechtigungsnachweis auf einem Gerät erstellt wird, das Passkeys unterstützt.

So senden Sie das Anmeldedatenobjekt an den Server:

  1. Codieren Sie die binären Parameter der Anmeldedaten als Base64URL, damit sie als String an den Server gesendet werden können. Dazu können Sie .toJSON() verwenden:

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);
  1. Senden Sie das Objekt in der nächsten Zeile an den Server:

public/client.js

return await _fetch('/auth/registerResponse', credential);

Wenn Sie das Programm ausführen, gibt der Server HTTP code 200 zurück. Das bedeutet, dass die Anmeldedaten registriert sind.

Jetzt haben Sie die vollständige Funktion registerCredential().

Lösungscode für diesen Abschnitt ansehen

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. UI zum Registrieren und Verwalten von Passkey-Anmeldedaten erstellen

Nachdem die registerCredential()-Funktion verfügbar ist, benötigen Sie eine Schaltfläche, um sie aufzurufen. Außerdem müssen Sie eine Liste der registrierten Passkeys anzeigen.

bfa4e7cdda47669e.png

Platzhalter-HTML hinzufügen

  1. Rufen Sie in Ihrem Editor die Datei views/home.html auf.
  2. Fügen Sie nach dem entsprechenden Kommentar einen UI-Platzhalter hinzu, in dem eine Schaltfläche zum Registrieren eines Passkeys und eine Liste von Passkeys angezeigt werden:

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>

Das Element div#list ist der Platzhalter für die Liste.

Passkey-Unterstützung prüfen

Damit die Option zum Erstellen eines Passkeys nur Nutzern mit Geräten angezeigt wird, die Passkeys unterstützen, müssen Sie zuerst prüfen, ob WebAuthn verfügbar ist. Wenn ja, müssen Sie die hidden-Klasse entfernen, damit die Schaltfläche Passkey erstellen angezeigt wird.

So prüfen Sie, ob eine Umgebung Passkeys unterstützt:

  1. Schreiben Sie am Ende der Datei views/home.html nach dem relevanten Kommentar eine Bedingung, die ausgeführt wird, wenn window.PublicKeyCredential, PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable und PublicKeyCredential.isConditionalMediationAvailable true sind.

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) {
  1. Prüfen Sie im Hauptteil der Bedingung, ob das Gerät einen Passkey erstellen kann, und dann, ob der Passkey beim automatischen Ausfüllen eines Formulars vorgeschlagen werden kann.

views/home.html

try {
    const capabilities = await PublicKeyCredential.getClientCapabilities();
    // Is conditional UI available in this browser?
    if (capabilities.conditionalGet === true &&
        capabilities.passkeyPlatformAuthenticator === true) {
  1. Wenn alle Bedingungen erfüllt sind, wird die Schaltfläche zum Erstellen eines Passkeys angezeigt. Andernfalls wird eine Warnmeldung angezeigt.

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.';
}

Registrierte Passkeys in einer Liste rendern

  1. Definieren Sie eine renderCredentials()-Funktion, die registrierte Passkeys vom Server abruft und in einer Liste rendert. Glücklicherweise haben Sie bereits den Serverendpunkt /auth/getKeys, um registrierte Passkeys für den angemeldeten Nutzer abzurufen.

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);
};
  1. Rufen Sie in der nächsten Zeile die Funktion renderCredentials() auf, um registrierte Passkeys anzuzeigen, sobald der Nutzer auf der Seite /home landet.

views/home.html

renderCredentials();

Passkey erstellen und registrieren

Wenn Sie einen Passkey erstellen und registrieren möchten, müssen Sie die Funktion registerCredential() aufrufen, die Sie zuvor implementiert haben.

So lösen Sie die registerCredential()-Funktion aus, wenn Sie auf die Schaltfläche Passkey erstellen klicken:

  1. Suchen Sie in der Datei nach dem Platzhalter-HTML nach der folgenden import-Anweisung:

views/home.html

import { 
  $, 
  _fetch, 
  loading, 
  updateCredential, 
  unregisterCredential, 
} from '/client.js';
  1. Fügen Sie am Ende des Texts der import-Anweisung die Funktion registerCredential() ein.

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';
  1. Definieren Sie am Ende der Datei nach dem relevanten Kommentar eine register()-Funktion, die die registerCredential()-Funktion und eine Lade-UI aufruft und renderCredentials() nach einer Registrierung aufruft. Hier wird klargestellt, dass der Browser einen Passkey erstellt und eine Fehlermeldung anzeigt, wenn etwas schiefgeht.

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();
  1. Fangen Sie Ausnahmen im Hauptteil der register()-Funktion ab. Die Methode navigator.credentials.create() löst einen InvalidStateError-Fehler aus, wenn auf dem Gerät bereits ein Passkey vorhanden ist. Dies wird mit dem excludeCredentials-Array untersucht. In diesem Fall wird dem Nutzer eine entsprechende Meldung angezeigt. Außerdem wird ein NotAllowedError-Fehler ausgegeben, wenn der Nutzer den Authentifizierungsdialog schließt. In diesem Fall ignorieren Sie sie einfach.

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);
    }
  }
};
  1. Hängen Sie in der Zeile nach der register()-Funktion die register()-Funktion an ein click-Ereignis für die Schaltfläche Passkey erstellen an.

views/home.html

createPasskey.addEventListener('click', register);

Lösungscode für diesen Abschnitt ansehen

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);

Jetzt ausprobieren

Wenn Sie alle bisherigen Schritte ausgeführt haben, haben Sie die Möglichkeit implementiert, Passkeys auf der Website zu erstellen, zu registrieren und anzuzeigen.

So probieren Sie es aus:

  1. Melden Sie sich auf der Website mit einem zufälligen Nutzernamen und Passwort an.
  2. Klicken Sie auf Passkey erstellen.
  3. Bestätigen Sie Ihre Identität mit der Displaysperre des Geräts.
  4. Prüfen Sie, ob ein Passkey registriert und im Abschnitt Ihre registrierten Passkeys der Webseite angezeigt wird.

Registrierte Passkeys, die auf der Seite „/home“ aufgeführt sind.

Registrierte Passkeys umbenennen und entfernen

Sie sollten die registrierten Passkeys in der Liste umbenennen oder löschen können. Sie können sich den Code im Codelab ansehen, um zu sehen, wie er funktioniert.

In Chrome können Sie registrierte Passkeys unter chrome://settings/passkeys auf dem Computer oder über den Passwortmanager in den Einstellungen auf Android-Geräten entfernen.

Informationen zum Umbenennen und Entfernen registrierter Passkeys auf anderen Plattformen finden Sie auf den entsprechenden Supportseiten für diese Plattformen.

5. Authentifizierung mit einem Passkey hinzufügen

Nutzer können jetzt einen Passkey erstellen und registrieren und ihn als sichere Authentifizierungsmethode für Ihre Website verwenden. Jetzt müssen Sie Ihrer Website eine Passkey-Authentifizierungsfunktion hinzufügen.

Funktion authenticate() erstellen

  • Erstellen Sie in der Datei public/client.js nach dem entsprechenden Kommentar eine Funktion namens authenticate(), die den Nutzer lokal und dann auf dem Server authentifiziert:

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.

};

Herausforderung und andere Optionen vom Serverendpunkt abrufen

Bevor Sie den Nutzer zur Authentifizierung auffordern, müssen Sie Parameter anfordern, die in WebAuthn vom Server übergeben werden sollen, einschließlich einer Challenge.

  • Rufen Sie im Text der Funktion authenticate() nach dem entsprechenden Kommentar die Funktion _fetch() auf, um eine POST-Anfrage an den Server zu senden:

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);

Der Server dieses Codelabs ist so konzipiert, dass er JSON zurückgibt, das dem PublicKeyCredentialRequestOptions-Wörterbuch, das an die WebAuthn-navigator.credentials.get()-API übergeben wird, so ähnlich wie möglich ist. Das folgende Code-Snippet enthält Beispieloptionen, die Sie erhalten sollten:

{
  "challenge": *****,
  "rpId": "localhost",
  "allowCredentials": []
}

Die folgende Tabelle ist nicht vollständig, enthält aber die wichtigen Parameter im PublicKeyCredentialRequestOptions-Dictionary:

Parameter

Textzeilen

challenge

Eine vom Server generierte Challenge in einem ArrayBuffer-Objekt. Dies ist erforderlich, um Wiederholungsversuche zu verhindern. Nehmen Sie niemals zweimal dieselbe Challenge in eine Antwort auf.

rpId

Eine RP‑ID ist eine Domain. Eine Website kann entweder ihre Domain oder ein registrierbares Suffix angeben. Dieser Wert muss mit dem Parameter rp.id übereinstimmen, der beim Erstellen des Passkeys verwendet wurde.

allowCredentials

Mit dieser Eigenschaft werden Authentifikatoren gefunden, die für diese Authentifizierung infrage kommen. Übergeben Sie ein leeres Array oder lassen Sie es nicht angegeben, damit der Browser eine Kontoauswahl anzeigt. Weitere InformationenallowCredentials

userVerification

Legen Sie den Wert auf "preferred" fest oder lassen Sie ihn weg, da dies der Standardwert ist. Gibt an, ob die Nutzerbestätigung über die Displaysperre des Geräts "required", "preferred" oder "discouraged" ist. Wenn Sie einen "preferred"-Wert festlegen, wird eine Nutzerbestätigung angefordert, wenn das Gerät dies unterstützt. Weitere Informationen zum Verhalten bei der Nutzerbestätigung

Nutzer lokal bestätigen und Anmeldedaten abrufen

  1. Konvertieren Sie im Funktionskörper der Funktion authenticate() nach dem relevanten Kommentar den Parameter challenge zurück in Binärformat:

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);
  1. Übergeben Sie ein leeres Array an den Parameter allowCredentials, um bei der Authentifizierung eines Nutzers eine Kontoauswahl zu öffnen:

public/client.js

// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];

Die Kontoauswahl verwendet die mit dem Passkey gespeicherten Informationen des Nutzers.

  1. Rufen Sie die Methode navigator.credentials.get() zusammen mit der Option mediation: 'conditional' auf:

public/client.js

// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
  publicKey: options,

  // Request a conditional UI.
  mediation: 'conditional'
});

Mit dieser Option wird der Browser angewiesen, Passkeys bedingt als Teil der automatischen Formularausfüllung vorzuschlagen.

Anmeldedaten bestätigen

Nachdem der Nutzer seine Identität lokal bestätigt hat, sollten Sie ein Anmeldedatenobjekt mit einer Signatur erhalten, die Sie auf dem Server überprüfen können.

Das folgende Code-Snippet enthält ein Beispiel für ein PublicKeyCredential-Objekt:

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "authenticatorData": *****,
    "signature": *****,
    "userHandle": *****
  },
  authenticatorAttachment: "platform"
}

Die folgende Tabelle ist nicht vollständig, enthält aber die wichtigen Parameter im PublicKeyCredential-Objekt:

Parameter

Textzeilen

id

Die Base64URL-codierte ID des authentifizierten Passkey-Anmeldedaten.

rawId

Eine ArrayBuffer-Objektversion der Berechtigungsnachweis-ID.

response.clientDataJSON

Ein ArrayBuffer-Objekt mit Clientdaten. Dieses Feld enthält Informationen wie die Challenge und den Ursprung, die der RP-Server überprüfen muss.

response.authenticatorData

Ein ArrayBuffer-Objekt mit Authenticator-Daten. Dieses Feld enthält Informationen wie die RP‑ID.

response.signature

Ein ArrayBuffer-Objekt der Signatur. Dieser Wert ist der Kern der Anmeldedaten und muss auf dem Server überprüft werden.

response.userHandle

Ein ArrayBuffer-Objekt, das die bei der Erstellung festgelegte Nutzer-ID enthält. Dieser Wert kann anstelle der Anmeldedaten-ID verwendet werden, wenn der Server die verwendeten ID-Werte auswählen muss oder wenn das Backend die Erstellung eines Index für Anmeldedaten-IDs vermeiden möchte.

authenticatorAttachment

Gibt einen "platform"-String zurück, wenn dieser Berechtigungsnachweis vom lokalen Gerät stammt. Andernfalls wird ein "cross-platform"-String zurückgegeben, insbesondere wenn der Nutzer sich mit einem Smartphone anmeldet. Wenn der Nutzer ein Smartphone für die Anmeldung verwenden muss, fordern Sie ihn auf, einen Passkey auf dem lokalen Gerät zu erstellen.

So senden Sie das Anmeldedatenobjekt an den Server:

  1. Codieren Sie im Hauptteil der Funktion authenticate() nach dem relevanten Kommentar die binären Parameter der Anmeldedaten, damit sie als String an den Server gesendet werden können. Dazu können Sie .toJSON() verwenden:

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);
  1. Senden Sie das Objekt an den Server:

public/client.js

return await _fetch(`/auth/signinResponse`, credential);

Wenn Sie das Programm ausführen, gibt der Server HTTP code 200 zurück. Das bedeutet, dass die Anmeldedaten bestätigt wurden.

Sie haben jetzt die vollständige Funktion authentication().

Lösungscode für diesen Abschnitt ansehen

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. Passkeys zur Browser-Autofill-Funktion hinzufügen

Wenn der Nutzer zurückkehrt, soll er sich so einfach und sicher wie möglich anmelden können. Wenn Sie der Anmeldeseite die Schaltfläche Mit Passkey anmelden hinzufügen, kann der Nutzer darauf tippen, im Kontoauswahlfeld des Browsers einen Passkey auswählen und die Identität über die Displaysperre bestätigen.

Die Umstellung von Passwörtern auf Passkeys erfolgt jedoch nicht für alle Nutzer gleichzeitig. Das bedeutet, dass Sie Passwörter erst entfernen können, wenn alle Nutzer auf Passkeys umgestellt haben. Bis dahin müssen Sie das Anmeldeformular mit Passwort beibehalten. Wenn Sie jedoch ein Passwortformular und eine Passkey-Schaltfläche einfügen, müssen Nutzer unnötigerweise auswählen, welche Methode sie für die Anmeldung verwenden möchten. Im Idealfall ist die Anmeldung unkompliziert.

Hier kommt eine bedingte Benutzeroberfläche ins Spiel. Eine bedingte Benutzeroberfläche ist eine WebAuthn-Funktion, mit der Sie ein Formulareingabefeld erstellen können, um zusätzlich zu Passwörtern auch einen Passkey als Teil von Autofill-Elementen vorzuschlagen. Wenn ein Nutzer in den Vorschlägen für die automatische Vervollständigung auf einen Passkey tippt, wird er aufgefordert, die Displaysperre des Geräts zu verwenden, um seine Identität lokal zu bestätigen. Das ist eine nahtlose Nutzererfahrung, da die Nutzeraktion fast identisch mit der einer passwortbasierten Anmeldung ist.

d616744939063451.png

Bedingte Benutzeroberfläche aktivieren

Um eine bedingte Benutzeroberfläche zu aktivieren, müssen Sie lediglich ein webauthn-Token in das Attribut autocomplete eines Eingabefelds einfügen. Wenn das Token festgelegt ist, können Sie die Methode navigator.credentials.get() mit dem String mediation: 'conditional' aufrufen, um die Benutzeroberfläche für die Displaysperre bedingt auszulösen.

  • Um eine bedingte Benutzeroberfläche zu aktivieren, ersetzen Sie die vorhandenen Eingabefelder für den Nutzernamen durch den folgenden HTML-Code nach dem entsprechenden Kommentar in der Datei 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>

Funktionen erkennen, WebAuthn aufrufen und eine bedingte Benutzeroberfläche aktivieren

  1. Ersetzen Sie in der Datei view/index.html nach dem entsprechenden Kommentar die vorhandene import-Anweisung durch den folgenden Code:

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";

Mit diesem Code wird die Funktion authenticate() importiert, die Sie zuvor implementiert haben.

  1. Prüfen Sie, ob das window.PulicKeyCredential-Objekt verfügbar ist und ob die PublicKeyCredential.isConditionalMediationAvailable()-Methode einen true-Wert zurückgibt. Rufen Sie dann die authenticate()-Funktion auf:

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);
    }
  }
}

Lösungscode für diesen Abschnitt ansehen

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);
    }
  }
}

Jetzt ausprobieren

Sie haben die Erstellung, Registrierung, Anzeige und Authentifizierung von Passkeys auf Ihrer Website implementiert.

So probieren Sie es aus:

  1. Rufen Sie den Tab „Vorschau“ auf.
  2. Melden Sie sich gegebenenfalls ab.
  3. Klicken Sie auf das Textfeld für den Nutzernamen. Ein Dialogfeld wird angezeigt.
  4. Wählen Sie das Konto aus, mit dem Sie sich anmelden möchten.
  5. Bestätigen Sie Ihre Identität mit der Displaysperre des Geräts. Sie werden zur Seite /home weitergeleitet und sind angemeldet.

Ein Dialogfeld, in dem Sie aufgefordert werden, Ihre Identität mit Ihrem gespeicherten Passwort oder Passkey zu bestätigen.

7. Glückwunsch!

Sie haben dieses Codelab abgeschlossen. Wenn Sie Fragen haben, stellen Sie sie auf der FIDO-DEV-Mailingliste oder auf Stack Overflow mit dem Tag passkey.

Weitere Informationen