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

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

О практической работе

subjectПоследнее обновление: мая 11, 2022
account_circleАвторы: Eiji Kitamura

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