Tworzenie pierwszej aplikacji WebAuthn

1. Zanim zaczniesz

Web Authentication API, nazywany też WebAuthn, umożliwia tworzenie i używanie danych logowania klucza publicznego o zakresie na poziomie źródła do uwierzytelniania użytkowników.

Interfejs API umożliwia korzystanie z modułów uwierzytelniających BLE, NFC i U2F lub USB FIDO2 (zwanych też kluczami bezpieczeństwa), a także uwierzytelniania platformy, które umożliwia użytkownikom uwierzytelnianie odciskiem palca lub blokadą ekranu.

W ramach tego ćwiczenia utworzysz witrynę z prostą funkcją ponownego uwierzytelniania, która wykorzystuje czytnik linii papilarnych. Ponowne uwierzytelnianie chroni dane konta, ponieważ użytkownicy, którzy już zalogowali się w witrynie, muszą ponownie przeprowadzić uwierzytelnianie, gdy próbują wpisać ważne sekcje witryny lub ponownie odwiedzić witrynę po upływie określonego czasu.

Wymagania wstępne

  • Podstawowe informacje o działaniu WebAuthn
  • Podstawowe umiejętności programowania z użyciem JavaScriptu

Jakie zadania wykonasz:

  • Tworzenie witryny za pomocą prostej funkcji ponownego uwierzytelniania, która wykorzystuje czytnik linii papilarnych

Czego potrzebujesz

  • Jedno z tych urządzeń:
    • Urządzenie z Androidem, najlepiej z czujnikiem biometrycznym
    • iPhone'a lub iPada z Touch ID lub Face ID w systemie iOS 14 lub nowszym
    • MacBook Pro lub Air z czytnikiem Touch ID w systemie macOS Big Sur lub nowszym
    • Windows 10 19H1 lub nowszy z zainstalowaną usługą Windows Hello
  • 1 z tych przeglądarek:
    • Google Chrome w wersji 67 lub nowszej
    • Microsoft Edge 85 lub nowsza wersja
    • Safari w wersji 14 lub nowszej.

2. Konfiguracja

W tym ćwiczeniu wykorzystaliśmy usługę o nazwie glitch. Możesz tutaj edytować kod JavaScript po stronie klienta i serwera i od razu je wdrażać.

Wejdź na https://glitch.com/edit":{"/webauthn-codelab-start.

Zobacz, jak to działa

Aby sprawdzić początkowy stan witryny:

  1. Kliknij 62bb7a6aac381af8.png Pokaż > 3343769d04c09851.png W nowym oknie, aby zobaczyć opublikowaną witrynę.
  2. Wpisz wybraną nazwę użytkownika i kliknij Dalej.
  3. Wpisz hasło i kliknij Zaloguj się.

Hasło jest ignorowane, ale Ty nadal jesteś uwierzytelniony. Jesteś na stronie głównej.

  1. Kliknij Wypróbuj ponowne uwierzytelnienie i powtórz drugi, trzeci i czwarty krok.
  2. Kliknij Wyloguj się.

Pamiętaj, że za każdym razem, gdy próbujesz się zalogować, musisz podać hasło. Emuluje on użytkownika, który musi ponownie się uwierzytelnić, zanim uzyska dostęp do ważnej sekcji witryny.

Remiksuj kod

  1. Otwórz WebAuthn / FIDO2 API Codelab.
  2. Kliknij nazwę projektu > Remix Project306122647ce93305.png, aby utworzyć rozwidlenie projektu i kontynuować tworzenie własnej wersji pod nowym adresem URL.

8d42bd24f0fd185c.png

3. Rejestrowanie danych logowania za pomocą odcisku palca

Musisz zarejestrować dane logowania wygenerowane przez UVPA, czyli wbudowaną funkcję uwierzytelniania, która weryfikuje tożsamość użytkownika. W zależności od urządzenia użytkownika zwykle jest to czytnik linii papilarnych.

Tę funkcję dodaj do strony /home:

260aab9f1a2587a7.png

Utwórz funkcję registerCredential()

Utwórz funkcję registerCredential(), która rejestruje nowe dane logowania.

publiczny/client.js

export const registerCredential = async () => {

};

Uzyskiwanie wyzwania i innych opcji z punktu końcowego serwera

Zanim poprosisz użytkownika o zarejestrowanie nowych danych logowania, musisz poprosić serwer o podanie parametrów zwracających WebAuthn, w tym testy zabezpieczające. Na szczęście masz już punkt końcowy serwera odpowiadający tym parametrom.

Dodaj ten kod do registerCredential().

publiczny/client.js

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

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

Protokół między serwerem a klientem nie jest częścią specyfikacji WebAuthn. Te ćwiczenia z programowania są zgodne ze specyfikacją WebAuthn, a obiekt JSON przekazywany do serwera jest bardzo podobny do PublicKeyCredentialCreationOptions, dzięki czemu jest intuicyjny. Poniższa tabela zawiera ważne parametry, które możesz przekazać do serwera, i wyjaśnia, do czego służą:

Parametry

Teksty reklam

attestation

Preferencja na atest – none, indirect lub direct. Wybierz none, chyba że go potrzebujesz.

excludeCredentials

Tablica PublicKeyCredentialDescriptor, aby mechanizm uwierzytelniający mógł uniknąć tworzenia duplikatów.

authenticatorSelection

authenticatorAttachment

Filtruj dostępnych uwierzytelniających. Jeśli chcesz, aby do urządzenia dołączono aplikację uwierzytelniającąplatform". W przypadku uwierzytelniających w roamingu użyj "cross-platform".

userVerification

Ustal, czy weryfikacja lokalna użytkownika w aplikacji uwierzytelniania to "required", "preferred" czy &"discouraged&quot. Jeśli chcesz uwierzytelniać odciskiem palca lub za pomocą blokady ekranu, użyj znaku &"required".

requireResidentKey

Użyj wartości true, jeśli utworzone dane logowania powinny być dostępne w przyszłym interfejsie UX konta.

Więcej informacji o tych opcjach znajdziesz w sekcji 5.4. Opcje tworzenia danych logowania (słownik PublicKeyCredentialCreationOptions).

Oto przykładowe opcje, które otrzymujesz z serwera.

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

Utwórz dane logowania

  1. Ponieważ te opcje są dostarczane zakodowane tak, aby przechodzą przez protokół HTTP, przekonwertuj niektóre parametry z powrotem na wartości binarne, a konkretnie ciągi user.id, challenge i wystąpienia id w tablicy excludeCredentials:

publiczny/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. Wywołaj metodę navigator.credentials.create(), aby utworzyć nowe dane logowania.

Podczas tej rozmowy przeglądarka wchodzi w interakcję z uwierzytelnianiem i próbuje zweryfikować tożsamość użytkownika przy użyciu UVPA.

publiczny/client.js

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

Gdy użytkownik potwierdzi swoją tożsamość, otrzymasz obiekt danych uwierzytelniających, który możesz wysłać na serwer i zarejestrować moduł uwierzytelniający.

Zarejestruj dane logowania w punkcie końcowym serwera

Oto przykładowy obiekt danych logowania, który powinien był do Ciebie dotrzeć.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. Tak jak w przypadku otrzymania obiektu opcji do zarejestrowania danych logowania, zakoduj parametry binarne w danych logowania, aby można je było przesyłać na serwer w postaci ciągu znaków:

publiczny/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. Przechowuj lokalnie dane logowania, aby móc ich używać do uwierzytelniania, gdy użytkownik wróci:

publiczny/client.js

localStorage.setItem(`credId`, credential.id);
  1. Wyślij obiekt na serwer, a jeśli zwróci HTTP code 200, spróbuj zarejestrować nowe dane logowania jako zarejestrowane.

publiczny/client.js

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

Możesz teraz korzystać z pełnej funkcji registerCredential().

Ostatni kod sekcji

publiczny/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. Utwórz interfejs użytkownika, aby rejestrować, pobierać i usuwać dane logowania

Dobrze jest mieć listę zarejestrowanych danych logowania i przycisków do ich usuwania.

9b5b5ae4a7b316bd.png

Obiekt zastępczy interfejsu użytkownika kompilacji

Dodaj interfejs użytkownika do listy danych logowania i przycisk do rejestracji nowych danych logowania. W zależności od tego, czy funkcja jest dostępna, możesz usunąć klasę hidden z komunikatu ostrzegawczego lub przycisku rejestracji nowych danych logowania. ul#list jest obiektem zastępczym dodawania listy zarejestrowanych danych logowania.

widoki/strona_glowna.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>

Wykrywanie funkcji i dostępność UVPA

Aby sprawdzić dostępność UVPA:

  1. Sprawdź window.PublicKeyCredential, aby sprawdzić, czy funkcja WebAuthn jest dostępna.
  2. Zadzwoń na numer PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(), aby sprawdzić, czy jest dostępny kod UVPA . Jeśli są dostępne, pokazuje się przycisk umożliwiający zarejestrowanie nowych danych logowania. Jeśli któreś z nich nie jest dostępne, zostanie wyświetlone ostrzeżenie.

widoki/strona_glowna.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');
}

Pobieranie i wyświetlanie listy danych logowania

  1. Utwórz funkcję getCredentials(), aby otrzymywać zarejestrowane dane logowania i wyświetlać je na liście. Na szczęście masz już w serwerze /auth/getKeys przydatny punkt końcowy, z którego możesz pobrać zarejestrowane dane logowania zalogowanego użytkownika.

Zwrócony plik JSON zawiera informacje logowania, takie jak id i publicKey. Możesz utworzyć kod HTML, aby wyświetlać je użytkownikom.

widoki/strona_glowna.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. Wywołaj getCredentials(), aby wyświetlić dane logowania, gdy użytkownik wejdzie na stronę /home.

widoki/strona_glowna.html

getCredentials();

Usuń dane logowania

Na liście danych logowania dodano przycisk pozwalający usunąć poszczególne dane logowania. Aby usunąć te zapytania, możesz wysłać żądanie do /auth/removeKey wraz z parametrem zapytania credId.

publiczny/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Dołącz unregisterCredential do istniejącej instrukcji import.

widoki/strona_glowna.html

import { _fetch, unregisterCredential } from '/client.js';
  1. Dodaj funkcję wywoływaną po kliknięciu przez użytkownika Usuń.

widoki/strona_glowna.html

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

Rejestrowanie danych logowania

Możesz wywołać registerCredential(), by zarejestrować nowe dane logowania, gdy użytkownik kliknie Dodaj dane logowania.

  1. Dołącz registerCredential do istniejącej instrukcji import.

widoki/strona_glowna.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. Wywołaj registerCredential() z opcjami dla navigator.credentials.create().

Nie zapomnij odnowić nazwy na koncie przez wywołanie getCredentials() po zarejestrowaniu.

widoki/strona_glowna.html

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

Teraz powinno być możliwe zarejestrowanie nowych danych logowania i wyświetlanie informacji na ich temat. Możesz wypróbować tę funkcję na swojej stronie internetowej.

Ostatni kod sekcji

widoki/strona_glowna.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>
...

publiczny/client.js

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

5. Uwierzytelnianie użytkownika odciskiem palca

Masz teraz zarejestrowane dane logowania i możesz ich używać do uwierzytelniania użytkowników. Teraz możesz dodać do witryny funkcję ponownego uwierzytelniania. Oto interfejs użytkownika:

Jeśli użytkownik wejdzie na stronę /reauth, zobaczy przycisk Uwierzytelnij, jeśli to możliwe. Uwierzytelnianie odciskiem palca (UVPA) rozpoczyna się, gdy użytkownik kliknie Uwierzytelnij, przejdzie uwierzytelnianie, a następnie otworzy się strona /home. Jeśli uwierzytelnianie biometryczne jest niedostępne lub uwierzytelnianie biometryczne nie powiedzie się, w interfejsie użytkownika zostanie użyty istniejący formularz hasła.

B8770c4e7475b075.png

Utwórz funkcję authenticate()

Utwórz funkcję o nazwie authenticate(), która weryfikuje tożsamość użytkownika odciskiem palca. Kod JavaScript dodaj tutaj:

publiczny/client.js

export const authenticate = async () => {

};

Uzyskiwanie wyzwania i innych opcji z punktu końcowego serwera

  1. Przed uwierzytelnieniem sprawdź, czy użytkownik ma zapisany identyfikator danych logowania, i ustaw go jako parametr zapytania, jeśli ma takie uprawnienia.

Gdy podasz identyfikator danych logowania oraz inne opcje, serwer będzie mógł podać dla Ciebie odpowiednie dane allowCredentials, dzięki czemu weryfikacja użytkownika będzie wiarygodna.

publiczny/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. Zanim poprosisz użytkownika o uwierzytelnienie, poproś serwer o wysłanie wyzwania i innych parametrów. Wywołaj _fetch() z argumentem opts, aby wysłać żądanie POST do serwera.

publiczny/client.js

const options = await _fetch(url, opts);

Oto przykłady opcji, które powinny się wyświetlić (jest zgodne z ustawieniem PublicKeyCredentialRequestOptions).

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

Najważniejsza opcja to allowCredentials. Gdy otrzymujesz opcje z serwera, atrybut allowCredentials powinien być pojedynczym obiektem w tablicy lub pustą tablicą w zależności od tego, czy dane logowania z identyfikatorem w parametrze zapytania są po stronie serwera.

  1. Rozwiązywanie problemu przy użyciu null, gdy allowCredentials jest pusta tablicą, aby interfejs użytkownika spytał o hasło.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Lokalna weryfikacja użytkownika i uzyskanie danych logowania

  1. Ponieważ te opcje są dostarczane zakodowane tak, by przechodzić przez protokół HTTP, przekonwertuj niektóre parametry z powrotem na binarne, a zwłaszcza challenge i wystąpienia id zawarte w tablicy allowCredentials:

publiczny/client.js

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

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Wywołaj metodę navigator.credentials.get(), aby zweryfikować tożsamość użytkownika za pomocą UVPA.

publiczny/client.js

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

Gdy użytkownik potwierdzi swoją tożsamość, powinien otrzymać obiekt danych uwierzytelniających, który można wysłać na serwer i w celu uwierzytelnienia użytkownika.

Weryfikowanie danych logowania

Oto przykład obiektu PublicKeyCredential (response to AuthenticatorAssertionResponse), który powinien być:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Zakoduj parametry binarne w danych logowania, aby można je było przesyłać na serwer w postaci ciągu znaków:

publiczny/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. Wyślij obiekt na serwer, a jeśli zwróci HTTP code 200, weź pod uwagę, że użytkownik został zalogowany.

publiczny/client.js

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

Możesz teraz korzystać z pełnej funkcji authentication().

Ostatni kod sekcji

publiczny/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. Włącz ponowne uwierzytelnianie

Interfejs kompilacji

Gdy użytkownik wróci, użytkownik powinien zadbać o to, aby jak najszybciej i bezpiecznie ponownie się uwierzytelnił. To właśnie tam dostrzec uwierzytelnianie biometryczne. Istnieją jednak przypadki uwierzytelniania biometrycznego:

  • UVPA jest niedostępny.
  • Użytkownik nie zarejestrował jeszcze żadnych danych logowania na swoim urządzeniu.
  • Pamięć jest wyczyszczona, a urządzenie nie pamięta już identyfikatora logowania.
  • Użytkownik z jakiegoś powodu nie może zweryfikować swojej tożsamości, na przykład palec jest mokry lub nosi maskę.

Dlatego zawsze warto przedstawić inne opcje logowania jako kreacje zastępcze. W tym ćwiczeniu wykorzystaliśmy formularz z hasłami opartymi na formularzach.

19da999b0145054.png

  1. Dodaj interfejs użytkownika, aby oprócz formularza hasła wyświetlać przycisk uwierzytelniania, który wywołuje uwierzytelnianie biometryczne.

Użyj klasy hidden, aby wyświetlić i ukryć jedną z nich w zależności od stanu użytkownika.

views/reauth.html

<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. Dołącz class="hidden" do formularza:

views/reauth.html

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

Wykrywanie funkcji i dostępność UVPA

Użytkownicy muszą logować się przy użyciu hasła, jeśli spełniasz jeden z tych warunków:

  • Uwierzytelnianie WebAuthn jest niedostępne.
  • UVPA jest niedostępny.
  • Nie można znaleźć identyfikatora danych logowania tego UVPA.

Selektywnie wyświetl lub ukryj przycisk uwierzytelniania:

views/reauth.html

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

Formularz powrotu do hasła

Użytkownik powinien też mieć możliwość zalogowania się za pomocą hasła.

Pokaż formularz hasła i ukryj przycisk uwierzytelniania, gdy użytkownik kliknie Zaloguj się za pomocą hasła:

views/reauth.html

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

C4A82800889f078c.png

Wywołaj uwierzytelnianie biometryczne

Na koniec włącz uwierzytelnianie biometryczne.

  1. Dołącz authenticate do istniejącej instrukcji import:

views/reauth.html

import { _fetch, authenticate } from '/client.js';
  1. Wywołaj authenticate(), gdy użytkownik kliknie Uwierzytelnij, aby rozpocząć uwierzytelnianie biometryczne.

Upewnij się, że nie udało się zakończyć uwierzytelniania biometrycznego.

views/reauth.html

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

Ostatni kod sekcji

views/reauth.html

...
    <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. Gratulacje!

Ćwiczenia z programowania zostały ukończone.

Więcej informacji

Specjalne podziękowania dla Yuriy Ackermann z FIDO Alliance za pomoc.