Erste WebAuthn-App erstellen

1. Hinweis

Mit der Web Authentication API, auch WebAuthn genannt, können Sie Anmeldedaten mit Ursprung auf Basis des öffentlichen Schlüssels erstellen und verwenden, um Nutzer zu authentifizieren.

Die API unterstützt die Verwendung von BLE-, NFC- und USB-Roaming-U2F- oder FIDO2-Authentifizierungs-Tools (auch bekannt als Sicherheitsschlüssel) sowie einer Plattform-Authentifizierung, über die Nutzer sich mit ihren Fingerabdrücken oder Displaysperren authentifizieren können.

In diesem Codelab erstellen Sie eine Website mit einer einfachen erneuten Authentifizierungsfunktion, für die ein Fingerabdrucksensor verwendet wird. Die erneute Authentifizierung schützt Kontodaten, da Nutzer, die sich bereits auf einer Website angemeldet haben, sich nach einer gewissen Zeit wieder anmelden müssen, wenn sie versuchen, wichtige Bereiche der Website einzugeben oder die Website neu aufzurufen.

Vorbereitung

  • Grundlegendes Verständnis von WebAuthn
  • Grundkenntnisse in Programmieren mit JavaScript

Aufgaben

  • Mit einer einfachen Authentifizierungsfunktion, die einen Fingerabdrucksensor nutzt, lässt sich eine Website erstellen

Voraussetzungen

  • Eines der folgenden Geräte:
    • Ein Android-Gerät, vorzugsweise mit biometrischem Sensor
    • Ein iPhone oder iPad mit Touch ID oder Face ID unter iOS 14 oder höher
    • MacBook Pro oder Air mit Touch ID unter macOS Big Sur oder höher
    • Windows 10 19H1 oder höher mit der Einrichtung von Windows Hello
  • Einer der folgenden Browser:
    • Google Chrome 67 oder höher
    • Microsoft Edge 85 oder höher
    • Safari 14 oder höher

2. Einrichten

In diesem Codelab nutzen Sie einen Dienst namens glitch. Hier können Sie clientseitigen und serverseitigen Code mit JavaScript bearbeiten und sofort bereitstellen.

Rufen Sie https://glitch.com/editaggregate/webauthn-codelab-start auf.

So funktionierts

Gehen Sie so vor, um den Anfangsstatus der Website zu sehen:

  1. Klicken Sie auf 62bb7a6aac381af8 Einblenden & 3343769d04c09851.png; In einem neuen Fenster, um die Live-Website aufzurufen.
  2. Geben Sie einen beliebigen Nutzernamen ein und klicken Sie auf Weiter.
  3. Geben Sie ein Passwort ein und klicken Sie auf Anmelden.

Das Passwort wird ignoriert, du bist aber weiterhin authentifiziert. Sie gelangen auf die Startseite.

  1. Klicken Sie auf Wiederholen und wiederholen Sie den zweiten, dritten und vierten Schritt.
  2. Wähle Abmelden aus.

Beachten Sie, dass Sie das Passwort jedes Mal eingeben müssen, wenn Sie sich anmelden. Sie emuliert einen Nutzer, der sich erneut authentifizieren muss, bevor er auf einen wichtigen Bereich einer Website zugreifen kann.

Code remixen

  1. Rufen Sie WebAuthn / FIDO2 API Codelab auf.
  2. Klicken Sie auf den Namen Ihres Projekts. Tippen Sie auf Projekt remixen306122647ce93305.png, um das Projekt zu verzweigen, und fahren Sie mit der eigenen Version unter einer neuen URL fort.

8d42bd24f0fd185c.png

3. Anmeldedaten mit einem Fingerabdruck registrieren

Sie müssen die von einem UVPA generierten Anmeldedaten in das Gerät einbauen und die Identität des Nutzers bestätigen. Je nach Gerät des Nutzers wird dieser Schalter in der Regel als Fingerabdrucksensor angesehen.

Sie fügen diese Funktion auf der Seite /home hinzu:

260aab9f1a2587a7

Funktion registerCredential() erstellen

Erstellt eine registerCredential()-Funktion, mit der neue Anmeldedaten registriert werden.

public/client.js

export const registerCredential = async () => {

};

Identitätsbestätigung und andere Optionen vom Serverendpunkt abrufen

Bevor Sie den Nutzer bitten, neue Anmeldedaten zu registrieren, bitten Sie ihn, die Parameter zurückzugeben, die WebAuthn, einschließlich einer Identitätsbestätigung, übergeben. Glücklicherweise haben Sie bereits einen Serverendpunkt, der mit solchen Parametern antwortet.

Fügen Sie registerCredential() den folgenden Code hinzu.

public/client.js

const opts = {
  attestation: 'none',
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'required',
    requireResidentKey: false
  }
};

const options = await _fetch('/auth/registerRequest', opts);

Das Protokoll zwischen einem Server und einem Client ist nicht Teil der WebAuthn-Spezifikation. Dieses Codelab ist jedoch auf die WebAuthn-Spezifikation und das JSON-Objekt ausgerichtet, das du an den Server übergibst, damit PublicKeyCredentialCreationOptions für dich intuitiv funktioniert. In der folgenden Tabelle sind die wichtigen Parameter aufgeführt, die Sie an den Server übergeben können:

Parameter

Textzeilen

attestation

Einstellung für die Attestierungsübertragung – none, indirect oder direct. Wählen Sie none aus, sofern Sie keine benötigen.

excludeCredentials

Array von PublicKeyCredentialDescriptor, damit der Authenticator die Erstellung doppelter Einträge vermeidet

authenticatorSelection

authenticatorAttachment

Filtern Sie die verfügbaren Authenticators. Wenn Sie eine Authentifizierung an das Gerät vornehmen möchten, verwenden Sie „platform“. Für Roaming-Authentifizierungen verwenden Sie „cross-platform“.

userVerification

Legen Sie fest, ob die lokale Nutzerbestätigung für die Authentifizierung und die requiredund die Fingerabdruck- oder Displaysperre erforderlich ist.required

requireResidentKey

Verwenden Sie true, wenn die erstellten Anmeldedaten für die zukünftige Auswahl der Kontoauswahl verfügbar sein sollen.

Weitere Informationen zu diesen Optionen finden Sie unter 5.4. Optionen für die Erstellung von Anmeldedaten (Wörterbuch PublicKeyCredentialCreationOptions)

Die folgenden Beispieloptionen erhalten Sie vom Server.

{
  "rp": {
    "name": "WebAuthn Codelab",
    "id": "webauthn-codelab.glitch.me"
  },
  "user": {
    "displayName": "User Name",
    "id": "...",
    "name": "test"
  },
  "challenge": "...",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }, {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "userVerification": "required"
  }
}

Anmeldedaten erstellen

  1. Da diese Optionen codiert sind, um das HTTP-Protokoll zu verwenden, konvertieren Sie einige Parameter zurück in das Binärprogramm, insbesondere user.id, challenge und Instanzen von id im excludeCredentials-Array:

public/client.js

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. Rufen Sie die Methode navigator.credentials.create() auf, um neue Anmeldedaten zu erstellen.

Bei diesem Aufruf interagiert der Browser mit dem Authenticator und versucht, die Identität des Nutzers mit der UVPA zu bestätigen.

public/client.js

const cred = await navigator.credentials.create({
  publicKey: options,
});

Sobald der Nutzer seine Identität bestätigt hat, sollten Sie ein Berechtigungsobjekt erhalten, das Sie an den Server senden und den Authenticator authentifizieren können.

Anmeldedaten am Serverendpunkt registrieren

Hier ist ein Beispiel für ein Berechtigungsobjekt, das Sie erhalten haben sollten.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. Wenn Sie ein Optionsobjekt zum Registrieren von Anmeldedaten erhalten haben, codieren Sie die binären Parameter der Anmeldedaten so, dass sie als String an den Server übertragen werden können:

public/client.js

const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
    base64url.encode(cred.response.attestationObject);
  credential.response = {
    clientDataJSON,
    attestationObject,
  };
}
  1. Speichern Sie die Anmeldedaten lokal, damit Sie sie zur Authentifizierung verwenden können, wenn der Nutzer zurückkehrt.

public/client.js

localStorage.setItem(`credId`, credential.id);
  1. Senden Sie das Objekt an den Server. Wenn HTTP code 200 zurückgegeben wird, prüfen Sie die neuen Anmeldedaten als erfolgreich.

public/client.js

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

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

Endgültiger Code für diesen Abschnitt

public/client.js

...
export const registerCredential = async () => {
  const opts = {
    attestation: 'none',
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      requireResidentKey: false
    }
  };

  const options = await _fetch('/auth/registerRequest', opts);

  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);
    }
  }
  
  const cred = await navigator.credentials.create({
    publicKey: options
  });

  const credential = {};
  credential.id =     cred.id;
  credential.rawId =  base64url.encode(cred.rawId);
  credential.type =   cred.type;

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const attestationObject =
      base64url.encode(cred.response.attestationObject);
    credential.response = {
      clientDataJSON,
      attestationObject
    };
  }

  localStorage.setItem(`credId`, credential.id);
  
  return await _fetch('/auth/registerResponse' , credential);
};
...

4. UI erstellen, um Anmeldedaten zu registrieren, abzurufen und zu entfernen

Eine Liste der registrierten Anmeldedaten und Schaltflächen können Sie löschen.

9b5b5ae4a7b316bd

UI-Platzhalter erstellen

UI zum Auflisten von Anmeldedaten und eine Schaltfläche zum Registrieren neuer Anmeldedaten hinzufügen. Je nachdem, ob die Funktion verfügbar ist oder nicht, entfernen Sie die hidden-Klasse entweder aus der Warnmeldung oder von der Schaltfläche, um neue Anmeldedaten zu registrieren. ul#list ist der Platzhalter zum Hinzufügen einer Liste registrierter Anmeldedaten.

views/home.html

<p id="uvpa_unavailable" class="hidden">
  This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
  Your registered credentials:
</h3>
<section>
  <div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>

Funktionserkennung und UVPA-Verfügbarkeit

Gehen Sie so vor, um die UVPA-Verfügbarkeit zu prüfen:

  1. Prüfen Sie window.PublicKeyCredential, um zu prüfen, ob WebAuthn verfügbar ist.
  2. Rufe PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() auf, um zu prüfen, ob ein UVPA verfügbar ist . Wenn sie verfügbar sind, wird die Schaltfläche zum Registrieren neuer Anmeldedaten angezeigt. Ist keines von beidem verfügbar, wird die Warnmeldung angezeigt.

views/home.html

const register = document.querySelector('#register');

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa) {
      register.classList.remove('hidden');
    } else {
      document
        .querySelector('#uvpa_unavailable')
        .classList.remove('hidden');
    }
  });        
} else {
  document
    .querySelector('#uvpa_unavailable')
    .classList.remove('hidden');
}

Liste mit Anmeldedaten abrufen und anzeigen

  1. Erstellen Sie eine getCredentials()-Funktion, um registrierte Anmeldedaten abzurufen und in einer Liste anzeigen zu lassen. Glücklicherweise haben Sie auf dem Server /auth/getKeys bereits einen praktischen Endpunkt, von dem Sie registrierte Anmeldedaten für den angemeldeten Nutzer abrufen können.

Der zurückgegebene JSON-Code enthält Informationen wie Anmeldedaten wie id und publicKey. Sie können HTML-Code erstellen, der den Nutzern präsentiert wird.

views/home.html

const getCredentials = async () => {
  const res = await _fetch('/auth/getKeys');
  const list = document.querySelector('#list');
  const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
    <div class="mdc-card credential">
      <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
      <pre class="public-key">${cred.publicKey}</pre>
      <div class="mdc-card__actions">
        <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
      </div>
    </div>`) : html`
    <p>No credentials found.</p>
    `}`;
  render(creds, list);
};
  1. Rufen Sie getCredentials() auf, um verfügbare Anmeldedaten anzuzeigen, sobald der Nutzer auf die Seite /home gelangt.

views/home.html

getCredentials();

Anmeldedaten entfernen

In der Liste der Anmeldedaten haben Sie eine Schaltfläche hinzugefügt, über die Sie die einzelnen Anmeldedaten entfernen können. Sie können eine Anfrage zusammen mit dem Suchparameter credId an /auth/removeKey senden, um sie zu entfernen.

public/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Hänge unregisterCredential an die vorhandene import-Anweisung an.

views/home.html

import { _fetch, unregisterCredential } from '/client.js';
  1. Fügen Sie eine Funktion hinzu, die aufgerufen wird, wenn der Nutzer auf Entfernen klickt.

views/home.html

const removeCredential = async e => {
  try {
    await unregisterCredential(e.target.id);
    getCredentials();
  } catch (e) {
    alert(e);
  }
};

Anmeldedaten registrieren

Sie können registerCredential() aufrufen, um neue Anmeldedaten zu registrieren, wenn der Nutzer auf Anmeldedaten hinzufügen klickt.

  1. Hänge registerCredential an die vorhandene import-Anweisung an.

views/home.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. Rufen Sie registerCredential() mit Optionen für navigator.credentials.create() auf.

Vergiss nicht, die Liste der Anmeldedaten zu verlängern, indem du nach der Registrierung getCredentials() aufrufst.

views/home.html

register.addEventListener('click', e => {
  registerCredential().then(user => {
    getCredentials();
  }).catch(e => alert(e));
});

Sie sollten jetzt in der Lage sein, neue Anmeldedaten zu registrieren und Informationen dazu anzeigen zu lassen. Probieren Sie es auf Ihrer Website aus.

Endgültiger Code für diesen Abschnitt

views/home.html

...
      <p id="uvpa_unavailable" class="hidden">
        This device does not support User Verifying Platform Authenticator. You can't register a credential.
      </p>
      <h3 class="mdc-typography mdc-typography--headline6">
        Your registered credentials:
      </h3>
      <section>
        <div id="list"></div>
        <mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
      </section>
      <mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
      <mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
    </main>
    <script type="module">
      import { _fetch, registerCredential, unregisterCredential } from '/client.js';
      import { html, render } from 'https://unpkg.com/lit-html@1.0.0/lit-html.js?module';

      const register = document.querySelector('#register');

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa) {
            register.classList.remove('hidden');
          } else {
            document
              .querySelector('#uvpa_unavailable')
              .classList.remove('hidden');
          }
        });        
      } else {
        document
          .querySelector('#uvpa_unavailable')
          .classList.remove('hidden');
      }

      const getCredentials = async () => {
        const res = await _fetch('/auth/getKeys');
        const list = document.querySelector('#list');
        const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
          <div class="mdc-card credential">
            <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
            <pre class="public-key">${cred.publicKey}</pre>
            <div class="mdc-card__actions">
              <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
            </div>
          </div>`) : html`
          <p>No credentials found.</p>
          `}`;
        render(creds, list);
      };

      getCredentials();

      const removeCredential = async e => {
        try {
          await unregisterCredential(e.target.id);
          getCredentials();
        } catch (e) {
          alert(e);
        }
      };

      register.addEventListener('click', e => {
        registerCredential({
          attestation: 'none',
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            userVerification: 'required',
            requireResidentKey: false
          }
        })
        .then(user => {
          getCredentials();
        })
        .catch(e => alert(e));
      });
    </script>
...

public/client.js

...
export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...

5. Nutzer mit einem Fingerabdruck authentifizieren

Sie verfügen jetzt über Anmeldedaten, die für die Authentifizierung des Nutzers verwendet werden können. Jetzt fügen Sie der Website eine neue Authentifizierungsfunktion hinzu. Für Nutzer:

Wenn der Nutzer auf die Seite /reauth gelangt, wird die Schaltfläche Authentifizieren angezeigt, wenn die biometrische Authentifizierung möglich ist. Die Authentifizierung mit einem Fingerabdruck (UVPA) beginnt, wenn sie auf Authentifizieren tippen, sich erfolgreich authentifizieren und dann zur Seite /home gelangen. Wenn keine biometrische Authentifizierung verfügbar ist oder eine Authentifizierung mit biometrischen Daten fehlschlägt, wird die vorhandene UI verwendet, um das vorhandene Passwortformular zu verwenden.

b8770c4e7475b075.png

Funktion authenticate() erstellen

Erstellen Sie eine Funktion namens „authenticate()“, die die Identität des Nutzers mit einem Fingerabdruck verifiziert. Hier fügen Sie JavaScript-Code hinzu:

public/client.js

export const authenticate = async () => {

};

Identitätsbestätigung und andere Optionen vom Serverendpunkt abrufen

  1. Prüfen Sie vor der Authentifizierung, ob der Nutzer eine Anmeldedaten-ID hat, und legen Sie sie als Suchparameter fest, wenn er dies tut.

Wenn Sie zusammen mit anderen Optionen eine Anmeldedaten-ID angeben, kann der Server die entsprechende allowCredentials bereitstellen. Dies macht die Bestätigung der Nutzer zuverlässig.

public/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. Bevor Sie den Nutzer zur Authentifizierung auffordern, sollte er eine Aufforderung zur Identitätsbestätigung und andere Parameter senden. Rufen Sie _fetch() mit opts als Argument auf, um eine POST-Anfrage an den Server zu senden.

public/client.js

const options = await _fetch(url, opts);

Im Folgenden finden Sie einige Beispieloptionen, die Sie erhalten sollten (stimmt mit PublicKeyCredentialRequestOptions überein).

{
  "challenge": "...",
  "timeout": 1800000,
  "rpId": "webauthn-codelab.glitch.me",
  "userVerification": "required",
  "allowCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ]
}

Die wichtigste Option ist hier allowCredentials. Wenn Sie Optionen vom Server erhalten, sollte allowCredentials entweder ein einzelnes Objekt in einem Array oder ein leeres Array sein, je nachdem, ob auf dem Server eine Anmeldedaten mit der ID im Suchparameter gefunden wird.

  1. Lösen Sie das Versprechen mit null, wenn allowCredentials ein leeres Array ist, sodass die UI wieder nach einem Passwort fragt.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Nutzer lokal bestätigen und Anmeldedaten erhalten

  1. Da diese Optionen codiert sind, um das HTTP-Protokoll zu verwenden, konvertieren Sie einige Parameter zurück in das Binärprogramm, insbesondere challenge und Instanzen von id im allowCredentials-Array:

public/client.js

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

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Rufen Sie die navigator.credentials.get()-Methode auf, um die Identität des Nutzers mit einem UVPA zu bestätigen.

public/client.js

const cred = await navigator.credentials.get({
  publicKey: options
});

Sobald der Nutzer seine Identität bestätigt hat, sollten Sie ein Anmeldeobjekt erhalten, das Sie an den Server senden und den Nutzer authentifizieren können.

Anmeldedaten überprüfen

Hier sehen Sie ein Beispiel für ein PublicKeyCredential-Objekt (response ist AuthenticatorAssertionResponse), das Sie erhalten haben sollten:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Codiere die binären Parameter der Anmeldedaten, damit sie als String an den Server gesendet werden können:

public/client.js

const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);

if (cred.response) {
  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. Wenn HTTP code 200 zurückgegeben wird, prüfen Sie den Nutzer als erfolgreich angemeldet:

public/client.js

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

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

Endgültiger Code für diesen Abschnitt

public/client.js

...
export const authenticate = async () => {
  const opts = {};

  let url = '/auth/signinRequest';
  const credId = localStorage.getItem(`credId`);
  if (credId) {
    url += `?credId=${encodeURIComponent(credId)}`;
  }
  
  const options = await _fetch(url, opts);
  
  if (options.allowCredentials.length === 0) {
    console.info('No registered credentials found.');
    return Promise.resolve(null);
  }

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

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

  const cred = await navigator.credentials.get({
    publicKey: options
  });

  const credential = {};
  credential.id = cred.id;
  credential.type = cred.type;
  credential.rawId = base64url.encode(cred.rawId);

  if (cred.response) {
    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. Erneute Authentifizierung aktivieren

Build-UI

Wenn der Nutzer zurückkehrt, sollte er sich einfach und sicher erneut authentifizieren. Hier kommt die biometrische Authentifizierung ins Spiel. Es gibt jedoch Fälle, in denen die biometrische Authentifizierung nicht funktioniert:

  • Die UVPA ist nicht verfügbar.
  • Der Nutzer hat noch keine Anmeldedaten auf seinem Gerät registriert.
  • Der Speicherinhalt wird gelöscht und das Gerät speichert die Anmeldedaten-ID nicht mehr.
  • Der Nutzer kann seine Identität nicht bestätigen, z. B. weil er nass ist oder er eine Maske trägt.

Aus diesem Grund solltest du immer andere Anmeldeoptionen als Fallbacks angeben. In diesem Codelab nutzen Sie eine formularbasierte Passwortlösung.

19da999b0145054.png

  1. UI hinzufügen, um eine Authentifizierungsschaltfläche einzublenden, mit der die biometrische Authentifizierung zusätzlich zum Passwortformular aufgerufen wird.

Mit der Klasse hidden kannst du eine davon nach Bedarf ein- und ausblenden.

views/reauth.html (in englischer Sprache)

<div id="uvpa_available" class="hidden">
  <h2>
    Verify your identity
  </h2>
  <div>
    <mwc-button id="reauth" raised>Authenticate</mwc-button>
  </div>
  <div>
    <mwc-button id="cancel">Sign-in with password</mwc-button>
  </div>
</div>
  1. Hängen Sie class="hidden" an das Formular an:

views/reauth.html (in englischer Sprache)

<form id="form" method="POST" action="/auth/password" class="hidden">

Funktionserkennung und UVPA-Verfügbarkeit

Nutzer müssen sich mit einem Passwort anmelden, wenn eine der folgenden Bedingungen erfüllt ist:

  • WebAuthn ist nicht verfügbar.
  • UVPA ist nicht verfügbar.
  • Die Anmeldedaten-ID für diese UVPA ist nicht sichtbar.

Lassen Sie die Schaltfläche für die Authentifizierung selektiv anzeigen oder ausblenden:

views/reauth.html (in englischer Sprache)

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa && localStorage.getItem(`credId`)) {
      document
        .querySelector('#uvpa_available')
        .classList.remove('hidden');
    } else {
      form.classList.remove('hidden');
    }
  });        
} else {
  form.classList.remove('hidden');
}

Fallback auf Passwortformular

Der Nutzer sollte auch die Möglichkeit haben, sich mit einem Passwort anzumelden.

Passwortformular anzeigen und Authentifizierungsschaltfläche ausblenden, wenn der Nutzer auf Mit Passwort anmelden klickt:

views/reauth.html (in englischer Sprache)

const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
  form.classList.remove('hidden');
  document
    .querySelector('#uvpa_available')
    .classList.add('hidden');
});

c4a82800889f078c.png

Biometrische Authentifizierung aufrufen

Abschließend aktivieren Sie die biometrische Authentifizierung.

  1. Hänge authenticate an die vorhandene import-Anweisung an:

views/reauth.html (in englischer Sprache)

import { _fetch, authenticate } from '/client.js';
  1. Rufe authenticate() auf, wenn der Nutzer auf Authentifizieren tippt, um die biometrische Authentifizierung zu starten.

Prüfen Sie, ob bei der biometrischen Authentifizierung ein Fehler auf das Passwortformular zurückgeht.

views/reauth.html (in englischer Sprache)

const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
  authenticate().then(user => {
    if (user) {
      location.href = '/home';
    } else {
      throw 'User not found.';
    }
  }).catch(e => {
    console.error(e.message || e);
    alert('Authentication failed. Use password to sign-in.');
    form.classList.remove('hidden');
    document.querySelector('#uvpa_available').classList.add('hidden');
  });        
});

Endgültiger Code für diesen Abschnitt

views/reauth.html (in englischer Sprache)

...
    <main class="content">
      <div id="uvpa_available" class="hidden">
        <h2>
          Verify your identity
        </h2>
        <div>
          <mwc-button id="reauth" raised>Authenticate</mwc-button>
        </div>
        <div>
          <mwc-button id="cancel">Sign-in with password</mwc-button>
        </div>
      </div>
      <form id="form" method="POST" action="/auth/password" class="hidden">
        <h2>
          Enter a password
        </h2>
        <input type="hidden" name="username" value="{{username}}" />
        <div class="mdc-text-field mdc-text-field--filled">
          <span class="mdc-text-field__ripple"></span>
          <label class="mdc-floating-label" id="password-label">password</label>
          <input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
          <span class="mdc-line-ripple"></span>
        </div>
        <input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
        <p class="instructions">password will be ignored in this demo.</p>
      </form>
    </main>
    <script src="https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
    <script type="module">
      new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
      import { _fetch, authenticate } from '/client.js';
      const form = document.querySelector('#form');
      form.addEventListener('submit', e => {
        e.preventDefault();
        const form = new FormData(e.target);
        const cred = {};
        form.forEach((v, k) => cred[k] = v);
        _fetch(e.target.action, cred)
        .then(user => {
          location.href = '/home';
        })
        .catch(e => alert(e));
      });

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa && localStorage.getItem(`credId`)) {
            document
              .querySelector('#uvpa_available')
              .classList.remove('hidden');
          } else {
            form.classList.remove('hidden');
          }
        });        
      } else {
        form.classList.remove('hidden');
      }

      const cancel = document.querySelector('#cancel');
      cancel.addEventListener('click', e => {
        form.classList.remove('hidden');
        document
          .querySelector('#uvpa_available')
          .classList.add('hidden');
      });

      const button = document.querySelector('#reauth');
      button.addEventListener('click', e => {
        authenticate().then(user => {
          if (user) {
            location.href = '/home';
          } else {
            throw 'User not found.';
          }
        }).catch(e => {
          console.error(e.message || e);
          alert('Authentication failed. Use password to sign-in.');
          form.classList.remove('hidden');
          document.querySelector('#uvpa_available').classList.add('hidden');
        });        
      });
    </script>
...

7. Glückwunsch!

Du hast dieses Codelab abgeschlossen.

Weitere Informationen

Wir möchten uns bei Yuriy Ackermann von der FIDO Alliance bedanken.