Passkeys mit Formularautofill in einer Webanwendung implementieren

1. Hinweis

Die Verwendung von Passkeys anstelle von Passwörtern ist eine gute Möglichkeit für Websites, ihre Nutzerkonten sicherer, einfacher und benutzerfreundlicher zu machen. 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 zugeordnet 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.

Voraussetzungen

Eine der folgenden Gerätekonfigurationen:

  • Google Chrome auf einem Android-Gerät mit Android 9 oder höher, vorzugsweise mit einem biometrischen Sensor.
  • Chrome auf einem Windows-Gerät mit Windows 10 oder höher.
  • Safari 16 oder höher auf einem iPhone mit iOS 16 oder höher oder auf einem iPad mit iPadOS 16 oder höher.
  • Safari 16 oder höher oder Chrome auf einem Apple-Desktopgerät mit macOS Ventura oder höher.

2. Einrichten

In diesem Codelab verwenden Sie den Dienst Glitch, mit dem Sie Client- und Serverseitencode mit JavaScript bearbeiten und direkt über den Browser bereitstellen können.

Projekt öffnen

  1. Öffnen Sie das Projekt in Glitch.
  2. Klicken Sie auf Remix, um das Glitch-Projekt zu forken.
  3. Klicken Sie im Navigationsmenü unten in Glitch auf Preview > Preview in a new window (Vorschau > Vorschau in einem neuen Fenster). In Ihrem Browser wird ein weiterer Tab geöffnet.

Die Schaltfläche „Vorschau in neuem Fenster“ im Navigationsmenü unten in Glitch

Ausgangszustand der Website prüfen

  1. Geben Sie auf dem Vorschautab 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 gelangen zur Startseite.
  3. Wenn Sie Ihren Anzeigenamen ändern möchten, tun Sie das. 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. Den Endzustand können Sie unter https://passkeys-codelab.glitch.me/ ausprobieren.

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 zugehörigen öffentlichen Schlüssel auf dem Server zu speichern.

Bei der Passkey-Erstellung wird ein Dialogfeld zur Nutzerbestätigung angezeigt.

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. Rufen Sie in Glitch die Datei public/client.js auf und scrollen Sie zum Ende.
  2. 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.

};

Mit dieser Funktion wird ein Passkey auf dem Server erstellt und registriert.

Herausforderung und andere Optionen vom Serverendpunkt abrufen

Bevor ein Passkey erstellt wird, müssen Sie Parameter an WebAuthn vom Server anfordern, 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 großer 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 sein Nutzername. Sie wird in der Kontoauswahl angezeigt. Wenn Sie einen Nutzernamen verwenden, geben Sie denselben Wert wie bei der Passwortauthentifizierung ein.

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, diesen 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 Verfahren 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.

authenticatorSelection.authenticatorAttachment

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

authenticatorSelection.requireResidentKey

Auf einen booleschen Wert true festgelegt. Ein auffindbarer Anmeldedatensatz (resident key) kann verwendet werden, ohne dass der Server die ID des Anmeldedatensatzes bereitstellen muss. Er ist also mit der Autofill-Funktion kompatibel.

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.

Anmeldedaten erstellen

  1. Konvertieren Sie im Funktionskörper von 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:

public/client.js

// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);

if (options.excludeCredentials) {
  for (let cred of options.excludeCredentials) {
    cred.id = base64url.decode(cred.id);
  }
}
  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 erkennbaren Anmeldedatenfunktion 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 ein passendes Passkey auf dem Gerät 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:

public/client.js

// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;

// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
  credential.authenticatorAttachment = cred.authenticatorAttachment;
}

// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject = base64url.encode(cred.response.attestationObject);

// Obtain transports.
const transports = cred.response.getTransports ? cred.response.getTransports() : [];

credential.response = {
  clientDataJSON,
  attestationObject,
  transports
};
  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 server endpoint.
  const options = await _fetch('/auth/registerRequest');
  
  // TODO: Add an ability to create a passkey: Create a credential.
  // Base64URL decode some values.

  options.user.id = base64url.decode(options.user.id);
  options.challenge = base64url.decode(options.challenge);

  if (options.excludeCredentials) {
    for (let cred of options.excludeCredentials) {
      cred.id = base64url.decode(cred.id);
    }
  }

  // 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.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
  if (cred.authenticatorAttachment) {
    credential.authenticatorAttachment = cred.authenticatorAttachment;
  }

  // Base64URL encode some values.
  const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
  const attestationObject =  
  base64url.encode(cred.response.attestationObject);

  // Obtain transports.
  const transports = cred.response.getTransports ? 
  cred.response.getTransports() : [];

  credential.response = {
    clientDataJSON,
    attestationObject,
    transports
  };

  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.

Registrierte Passkeys auf der Seite „/home“

Platzhalter-HTML hinzufügen

  1. Rufen Sie in Glitch die Datei views/home.html auf.
  2. Fügen Sie nach dem entsprechenden Kommentar einen UI-Platzhalter ein, 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 class="mdc-typography mdc-typography--headline6"> Your registered 
  passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-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 Klasse hidden 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 entsprechenden 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 results = await Promise.all([

    // Is platform authenticator available in this browser?
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),

    // Is conditional UI available in this browser?
    PublicKeyCredential.isConditionalMediationAvailable()
  ]);
  1. Wenn alle Bedingungen erfüllt sind, wird die Schaltfläche zum Erstellen eines Passkeys angezeigt. Andernfalls wird eine Warnmeldung angezeigt.

views/home.html

    if (results.every(r => r === 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.';
}

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 = html`${res.length > 0 ? html`
    <mwc-list>
      ${res.map(cred => html`
        <mwc-list-item>
          <div class="list-item">
            <div class="entity-name">
              <span>${cred.name || 'Unnamed' }</span>
          </div>
          <div class="buttons">
            <mwc-icon-button data-cred-id="${cred.id}"  
            data-name="${cred.name || 'Unnamed' }" @click="${rename}"  
            icon="edit"></mwc-icon-button>
            <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" 
            icon="delete"></mwc-icon-button>
          </div>
         </div>
      </mwc-list-item>`)}
  </mwc-list>` : html`
  <mwc-list>
    <mwc-list-item>No credentials found.</mwc-list-item>
  </mwc-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. Es 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() gibt 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 relevante Nachricht angezeigt. Außerdem wird ein NotAllowedError-Fehler ausgegeben, wenn der Nutzer das Authentifizierungsdialogfeld 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 class="mdc-typography mdc-typography--headline6"> Your registered  
  passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-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');

// Feature detections
if (window.PublicKeyCredential &&
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  try {
    const results = await Promise.all([

      // Is platform authenticator available in this browser?
      PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),

      // Is conditional UI available in this browser?
      PublicKeyCredential.isConditionalMediationAvailable()
    ]);
    if (results.every(r => r === 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`
  <mwc-list>
    ${res.map(cred => html`
      <mwc-list-item>
        <div class="list-item">
          <div class="entity-name">
            <span>${cred.name || 'Unnamed' }</span>
          </div>
          <div class="buttons">
            <mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>
            <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>
          </div>
        </div>
      </mwc-list-item>`)}
  </mwc-list>` : html`
  <mwc-list>
    <mwc-list-item>No credentials found.</mwc-list-item>
  </mwc-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 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);
    }
  }
};

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 dem Vorschau-Tab 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 ansehen, wie es im Code funktioniert, da es im Codelab enthalten ist.

In Chrome können Sie registrierte Passkeys auf dem Computer unter chrome://settings/passkeys oder auf Android-Geräten im Passwortmanager in den Einstellungen 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 überprüft:

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.
const options = await _fetch('/auth/signinRequest');

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": "passkeys-codelab.glitch.me",
  "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 Herausforderung in einer Antwort an. Es handelt sich um ein CSRF-Token.

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

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.

Nutzer lokal bestätigen und Anmeldedaten abrufen

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

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 überprüfen

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 Authentifikatordaten. 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 entsprechenden Kommentar die binären Parameter der Anmeldedaten, damit sie als String an den Server gesendet werden können:

public/client.js

// 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,
};
  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 auf die Schaltfläche 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 der Autofill-Elemente vorzuschlagen. Wenn ein Nutzer in den Vorschlägen für die automatische Vervollständigung auf einen Passkey tippt, wird er aufgefordert, seine Identität lokal mit der Displaysperre des Geräts zu bestätigen. Das ist eine nahtlose Nutzererfahrung, da die Nutzeraktion fast identisch mit der einer passwortbasierten Anmeldung ist.

Ein Passkey, der im Rahmen des automatischen Ausfüllens von Formularen vorgeschlagen wird.

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 nach dem entsprechenden Kommentar in der Datei view/index.html durch den folgenden HTML-Code:

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus />

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.isConditionalMediationAvailable
) {
  try {

    // Is conditional UI available in this browser?
    const cma =
      await PublicKeyCredential.isConditionalMediationAvailable();
    if (cma) {

      // 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. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus 
/>

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 avaiable in this browser?
if (window.PublicKeyCredential &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  try {

    // Is a conditional UI available in this browser?
    const cma= await PublicKeyCredential.isConditionalMediationAvailable();
    if (cma) {

      // If a 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