Создайте свое первое приложение WebAuthn

1. Прежде чем начать

API-интерфейс веб-аутентификации, также известный как WebAuthn, позволяет создавать и использовать учетные данные с открытым ключом на уровне источника для аутентификации пользователей.

API поддерживает использование аутентификаторов BLE, NFC и USB-роуминга U2F или FIDO2, также известных как ключи безопасности, а также аутентификатора платформы, который позволяет пользователям аутентифицироваться с помощью отпечатков пальцев или блокировки экрана.

В этой лаборатории кода вы создаете веб-сайт с простой функцией повторной аутентификации, использующей датчик отпечатков пальцев. Повторная аутентификация защищает данные учетной записи, поскольку требует от пользователей, которые уже вошли на веб-сайт, повторную аутентификацию, когда они пытаются войти в важные разделы веб-сайта или повторно посетить веб-сайт через определенное время.

Предпосылки

  • Базовое понимание того, как работает WebAuthn
  • Базовые навыки программирования на JavaScript

Что ты будешь делать

  • Создайте веб-сайт с простой функцией повторной аутентификации, использующей датчик отпечатков пальцев.

Что вам понадобится

  • Одно из следующих устройств:
    • Android-устройство, желательно с биометрическим датчиком
    • iPhone или iPad с Touch ID или Face ID на iOS 14 или выше
    • MacBook Pro или Air с Touch ID на macOS Big Sur или выше
    • Windows 10 19H1 или выше с настройкой Windows Hello
  • Один из следующих браузеров:
    • Google Chrome 67 или выше
    • Microsoft Edge 85 или выше
    • Сафари 14 или выше

2. Настройте

В этой кодлабе вы используете сервис под названием glitch . Здесь вы можете редактировать клиентский и серверный код с помощью JavaScript и мгновенно развертывать их.

Перейдите к https://glitch.com/edit/#!/webauthn-codelab-start .

Увидеть как это работает

Выполните следующие действия, чтобы увидеть начальное состояние веб-сайта:

  1. Нажмите 62bb7a6aac381af8.png Показать > 3343769d04c09851.png В новом окне , чтобы увидеть живой веб-сайт .
  2. Введите имя пользователя по вашему выбору и нажмите « Далее ».
  3. Введите пароль и нажмите Войти .

Пароль игнорируется, но вы все еще аутентифицированы. Вы попадаете на главную страницу.

  1. Щелкните Try reauth и повторите второй, третий и четвертый шаги.
  2. Щелкните Выйти .

Обратите внимание, что вы должны вводить пароль каждый раз, когда пытаетесь войти в систему. Это эмулирует пользователя, которому необходимо повторно пройти аутентификацию, прежде чем он сможет получить доступ к важному разделу веб-сайта.

Смешайте код

  1. Перейдите к WebAuthn/FIDO2 API Codelab .
  2. Нажмите на название вашего проекта > Remix Project . 306122647ce93305.png чтобы разветвить проект и продолжить работу с собственной версией по новому URL-адресу.

8d42bd24f0fd185c.png

3. Зарегистрируйте учетные данные с отпечатком пальца

Вам необходимо зарегистрировать учетные данные, сгенерированные UVPA, аутентификатором, встроенным в устройство и проверяющим личность пользователя. Обычно это рассматривается как датчик отпечатков пальцев в зависимости от устройства пользователя.

Вы добавляете эту функцию на /home страницу:

260aab9f1a2587a7.png

Создать функцию registerCredential()

Создайте функцию registerCredential() , которая регистрирует новые учетные данные.

общедоступный/client.js

export const registerCredential = async () => {

};

Получите вызов и другие параметры с конечной точки сервера.

Прежде чем попросить пользователя зарегистрировать новые учетные данные, запросите, чтобы сервер вернул параметры для передачи в WebAuthn, включая вызов. К счастью, у вас уже есть конечная точка сервера, которая отвечает такими параметрами.

Добавьте следующий код в registerCredential() .

общедоступный/client.js

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

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

Протокол между сервером и клиентом не является частью спецификации WebAuthn. Однако эта кодовая лаборатория предназначена для согласования со спецификацией WebAuthn, а объект JSON, который вы передаете на сервер, очень похож на PublicKeyCredentialCreationOptions , поэтому он интуитивно понятен для вас. Следующая таблица содержит важные параметры, которые вы можете передать на сервер, и объясняет, что они делают:

Параметры

Описания

attestation

Предпочтение в отношении передачи аттестации — none , indirect или direct . Не выбирайте none , если он вам не нужен.

excludeCredentials

Массив PublicKeyCredentialDescriptor , чтобы аутентификатор мог избежать создания дубликатов.

authenticatorSelection

authenticatorAttachment

Фильтровать доступные аутентификаторы. Если вы хотите, чтобы к устройству был подключен аутентификатор, используйте « platform ». Для перемещаемых аутентификаторов используйте « cross-platform ».

userVerification

Определите, является ли проверка локального пользователя аутентификатора « required », « preferred » или « discouraged ». Если вы хотите аутентификацию по отпечатку пальца или блокировке экрана, используйте « required ».

requireResidentKey

Используйте true , если созданные учетные данные должны быть доступны для будущего пользовательского интерфейса средства выбора учетной записи.

Чтобы узнать больше об этих опциях, см . 5.4. Параметры создания учетных данных (словарь PublicKeyCredentialCreationOptions ) .

Ниже приведены примеры параметров, которые вы получаете от сервера.

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

Создать учетные данные

  1. Поскольку эти параметры доставляются закодированными для прохождения через протокол HTTP, преобразуйте некоторые параметры обратно в двоичные, в частности, user.id , challenge и экземпляры id , включенные в массив excludeCredentials :

общедоступный/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. Вызовите метод navigator.credentials.create() , чтобы создать новые учетные данные.

С помощью этого вызова браузер взаимодействует с аутентификатором и пытается проверить личность пользователя с помощью UVPA.

общедоступный/client.js

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

Как только пользователь подтвердит свою личность, вы должны получить объект учетных данных, который вы можете отправить на сервер и зарегистрировать аутентификатор.

Зарегистрируйте учетные данные на конечной точке сервера

Вот пример объекта учетных данных, который вы должны были получить.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. Как и в случае, когда вы получили объект опции для регистрации учетных данных, закодируйте двоичные параметры учетных данных, чтобы их можно было доставить на сервер в виде строки:

общедоступный/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. Сохраните идентификатор учетных данных локально, чтобы вы могли использовать его для аутентификации, когда пользователь вернется:

общедоступный/client.js

localStorage.setItem(`credId`, credential.id);
  1. Отправьте объект на сервер и, если он вернет HTTP code 200 , считайте, что новые учетные данные успешно зарегистрированы.

общедоступный/client.js

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

Теперь у вас есть полная функция registerCredential() !

Окончательный код для этого раздела

общедоступный/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. Создайте пользовательский интерфейс для регистрации, получения и удаления учетных данных.

Хорошо иметь список зарегистрированных учетных данных и кнопки для их удаления.

9b5b5ae4a7b316bd.png

Заполнитель пользовательского интерфейса сборки

Добавьте пользовательский интерфейс для списка учетных данных и кнопку для регистрации новых учетных данных. В зависимости от того, доступна функция или нет, вы удаляете hidden класс либо из предупреждающего сообщения, либо из кнопки для регистрации новых учетных данных. ul#list — это заполнитель для добавления списка зарегистрированных учетных данных.

просмотры/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>

Обнаружение признаков и доступность UVPA

Выполните следующие действия, чтобы проверить доступность UVPA:

  1. Изучите window.PublicKeyCredential , чтобы проверить, доступен ли WebAuthn.
  2. Вызовите PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() , чтобы проверить, доступен ли UVPA. Если они доступны, вы показываете кнопку для регистрации новых учетных данных. Если какой-либо из них недоступен, вы показываете предупреждающее сообщение.

просмотры/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');
}

Получить и отобразить список учетных данных

  1. Создайте функцию getCredentials() , чтобы вы могли получать зарегистрированные учетные данные и отображать их в списке. К счастью, у вас уже есть удобная конечная точка на сервере /auth/getKeys из которой вы можете получить зарегистрированные учетные данные для вошедшего в систему пользователя.

Возвращаемый JSON включает учетную информацию, такую ​​как id и publicKey . Вы можете создать HTML, чтобы показать их пользователю.

просмотры/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. getCredentials() для отображения доступных учетных данных, как только пользователь попадет на страницу /home .

просмотры/home.html

getCredentials();

Удалить учетные данные

В список учетных данных вы добавили кнопку для удаления каждого из учетных данных. Вы можете отправить запрос на /auth/removeKey вместе с параметром запроса credId , чтобы удалить их.

общедоступный/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Добавьте unregisterCredential к существующему оператору import .

просмотры/home.html

import { _fetch, unregisterCredential } from '/client.js';
  1. Добавьте функцию, которая будет вызываться, когда пользователь нажимает кнопку « Удалить ».

просмотры/home.html

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

Зарегистрировать учетные данные

Вы можете вызвать registerCredential() для регистрации новых учетных данных, когда пользователь нажимает Добавить учетные данные .

  1. Добавьте registerCredential к существующему оператору import .

просмотры/home.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. Вызвать registerCredential() с параметрами navigator.credentials.create() .

Не забудьте обновить список учетных данных, вызвав getCredentials() после регистрации.

просмотры/home.html

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

Теперь вы сможете зарегистрировать новые учетные данные и отобразить информацию о них. Вы можете попробовать это на своем живом веб-сайте.

Окончательный код для этого раздела

просмотры/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>
...

общедоступный/client.js

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

5. Аутентифицируйте пользователя по отпечатку пальца

Теперь у вас есть учетные данные, зарегистрированные и готовые к использованию в качестве способа аутентификации пользователя. Теперь вы добавляете на веб-сайт функцию повторной аутентификации. Вот пользовательский опыт:

Когда пользователь попадает на страницу /reauth , он видит кнопку Authenticate , если возможна биометрическая аутентификация. Аутентификация с помощью отпечатка пальца (UVPA) начинается, когда они нажимают « Аутентификация » , успешно проходят аутентификацию, а затем попадают на страницу /home . Если биометрическая аутентификация недоступна или биометрическая аутентификация не удалась, пользовательский интерфейс возвращается к использованию существующей формы пароля.

b8770c4e7475b075.png

Создать функцию authenticate()

Создайте функцию с именем authenticate() , которая проверяет личность пользователя с помощью отпечатка пальца. Вы добавляете код JavaScript здесь:

общедоступный/client.js

export const authenticate = async () => {

};

Получите вызов и другие параметры с конечной точки сервера.

  1. Перед аутентификацией проверьте, есть ли у пользователя сохраненный идентификатор учетных данных, и установите его в качестве параметра запроса, если он есть.

Когда вы указываете идентификатор учетных данных вместе с другими параметрами, сервер может предоставить соответствующие allowCredentials и это делает проверку пользователя надежной.

общедоступный/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. Прежде чем просить пользователя пройти аутентификацию, попросите сервер отправить запрос и другие параметры. Вызовите _fetch() с opts в качестве аргумента, чтобы отправить запрос POST на сервер.

общедоступный/client.js

const options = await _fetch(url, opts);

Вот примеры параметров, которые вы должны получить (согласуется с PublicKeyCredentialRequestOptions ).

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

Самый важный параметр здесь — allowCredentials . Когда вы получаете параметры от сервера, allowCredentials должен быть либо отдельным объектом в массиве, либо пустым массивом в зависимости от того, найдены ли учетные данные с идентификатором в параметре запроса на стороне сервера.

  1. Разрешите обещание с null значением, когда allowCredentials является пустым массивом, чтобы пользовательский интерфейс возвращался к запросу пароля.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Локально проверьте пользователя и получите учетные данные

  1. Поскольку эти параметры доставляются закодированными для прохождения через протокол HTTP, преобразуйте некоторые параметры обратно в двоичные, в частности, challenge и экземпляры id , включенные в массив allowCredentials :

общедоступный/client.js

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

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Вызовите метод navigator.credentials.get() , чтобы проверить личность пользователя с помощью UVPA.

общедоступный/client.js

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

Как только пользователь подтвердит свою личность, вы должны получить объект учетных данных, который вы можете отправить на сервер и аутентифицировать пользователя.

Подтвердите учетные данные

Вот пример объекта PublicKeyCredential ( responseAuthenticatorAssertionResponse ), который вы должны были получить:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Закодируйте двоичные параметры учетных данных, чтобы их можно было доставить на сервер в виде строки:

общедоступный/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. Отправьте объект на сервер и, если он вернет HTTP code 200 , считайте, что пользователь успешно выполнил вход:

общедоступный/client.js

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

Теперь у вас есть полная функция authentication() !

Окончательный код для этого раздела

общедоступный/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. Включите повторную аутентификацию

Построить пользовательский интерфейс

Когда пользователь возвращается, вы хотите, чтобы он повторно аутентифицировался как можно проще и безопаснее. Вот где сияет биометрическая аутентификация. Однако есть случаи, когда биометрическая аутентификация может не работать:

  • УФПА недоступен.
  • Пользователь еще не зарегистрировал учетные данные на своем устройстве.
  • Хранилище очищено, и устройство больше не помнит идентификатор учетных данных.
  • Пользователь не может подтвердить свою личность по какой-либо причине, например, когда его палец мокрый или он носит маску.

Вот почему всегда важно предоставлять другие варианты входа в качестве запасных вариантов. В этой лаборатории кода вы используете решение для пароля на основе формы.

19da999b0145054.png

  1. Добавьте пользовательский интерфейс, чтобы показать кнопку аутентификации, которая вызывает биометрическую аутентификацию в дополнение к форме пароля.

Используйте hidden класс для выборочного отображения и скрытия одного из них в зависимости от состояния пользователя.

просмотры/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. Добавьте class="hidden" к форме:

просмотры/reauth.html

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

Обнаружение признаков и доступность UVPA

Пользователи должны входить в систему с паролем, если выполняется одно из следующих условий:

  • WebAuthn недоступен.
  • УФПА недоступно.
  • Идентификатор учетных данных для этого UVPA невозможно обнаружить.

Выборочно показать кнопку аутентификации или скрыть ее:

просмотры/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');
}

Возврат к форме пароля

Пользователь также должен иметь возможность выбрать вход с паролем.

Показать форму пароля и скрыть кнопку аутентификации, когда пользователь нажимает Войти с паролем :.

просмотры/reauth.html

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

c4a82800889f078c.png

Активировать биометрическую аутентификацию

Наконец, включите биометрическую аутентификацию.

  1. Добавьте authenticate к существующему оператору import :

просмотры/reauth.html

import { _fetch, authenticate } from '/client.js';
  1. Вызовите authenticate() , когда пользователь нажимает Authenticate , чтобы начать биометрическую аутентификацию.

Убедитесь, что ошибка биометрической аутентификации возвращается к форме пароля.

просмотры/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');
  });        
});

Окончательный код для этого раздела

просмотры/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. Поздравляем!

Вы закончили эту лабораторную работу!

Учить больше

Отдельное спасибо Юрию Акерманну из FIDO Alliance за помощь.