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 .
Увидеть как это работает
Выполните следующие действия, чтобы увидеть начальное состояние веб-сайта:
- Нажмите Показать > В новом окне , чтобы увидеть живой веб-сайт .
- Введите имя пользователя по вашему выбору и нажмите « Далее ».
- Введите пароль и нажмите Войти .
Пароль игнорируется, но вы все еще аутентифицированы. Вы попадаете на главную страницу.
- Щелкните Try reauth и повторите второй, третий и четвертый шаги.
- Щелкните Выйти .
Обратите внимание, что вы должны вводить пароль каждый раз, когда пытаетесь войти в систему. Это эмулирует пользователя, которому необходимо повторно пройти аутентификацию, прежде чем он сможет получить доступ к важному разделу веб-сайта.
Смешайте код
- Перейдите к WebAuthn/FIDO2 API Codelab .
- Нажмите на название вашего проекта > Remix Project . чтобы разветвить проект и продолжить работу с собственной версией по новому URL-адресу.
3. Зарегистрируйте учетные данные с отпечатком пальца
Вам необходимо зарегистрировать учетные данные, сгенерированные UVPA, аутентификатором, встроенным в устройство и проверяющим личность пользователя. Обычно это рассматривается как датчик отпечатков пальцев в зависимости от устройства пользователя.
Вы добавляете эту функцию на /home
страницу:
Создать функцию 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
, поэтому он интуитивно понятен для вас. Следующая таблица содержит важные параметры, которые вы можете передать на сервер, и объясняет, что они делают:
Параметры | Описания | ||
| Предпочтение в отношении передачи аттестации — | ||
| Массив | ||
| | Фильтровать доступные аутентификаторы. Если вы хотите, чтобы к устройству был подключен аутентификатор, используйте « | |
| Определите, является ли проверка локального пользователя аутентификатора « | ||
| Используйте |
Чтобы узнать больше об этих опциях, см . 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"
}
}
Создать учетные данные
- Поскольку эти параметры доставляются закодированными для прохождения через протокол 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);
}
}
- Вызовите метод
navigator.credentials.create()
, чтобы создать новые учетные данные.
С помощью этого вызова браузер взаимодействует с аутентификатором и пытается проверить личность пользователя с помощью UVPA.
общедоступный/client.js
const cred = await navigator.credentials.create({
publicKey: options,
});
Как только пользователь подтвердит свою личность, вы должны получить объект учетных данных, который вы можете отправить на сервер и зарегистрировать аутентификатор.
Зарегистрируйте учетные данные на конечной точке сервера
Вот пример объекта учетных данных, который вы должны были получить.
{
"id": "...",
"rawId": "...",
"type": "public-key",
"response": {
"clientDataJSON": "...",
"attestationObject": "..."
}
}
- Как и в случае, когда вы получили объект опции для регистрации учетных данных, закодируйте двоичные параметры учетных данных, чтобы их можно было доставить на сервер в виде строки:
общедоступный/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,
};
}
- Сохраните идентификатор учетных данных локально, чтобы вы могли использовать его для аутентификации, когда пользователь вернется:
общедоступный/client.js
localStorage.setItem(`credId`, credential.id);
- Отправьте объект на сервер и, если он вернет
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. Создайте пользовательский интерфейс для регистрации, получения и удаления учетных данных.
Хорошо иметь список зарегистрированных учетных данных и кнопки для их удаления.
Заполнитель пользовательского интерфейса сборки
Добавьте пользовательский интерфейс для списка учетных данных и кнопку для регистрации новых учетных данных. В зависимости от того, доступна функция или нет, вы удаляете 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:
- Изучите
window.PublicKeyCredential
, чтобы проверить, доступен ли WebAuthn. - Вызовите
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');
}
Получить и отобразить список учетных данных
- Создайте функцию
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);
};
-
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)}`);
};
- Добавьте
unregisterCredential
к существующему операторуimport
.
просмотры/home.html
import { _fetch, unregisterCredential } from '/client.js';
- Добавьте функцию, которая будет вызываться, когда пользователь нажимает кнопку « Удалить ».
просмотры/home.html
const removeCredential = async e => {
try {
await unregisterCredential(e.target.id);
getCredentials();
} catch (e) {
alert(e);
}
};
Зарегистрировать учетные данные
Вы можете вызвать registerCredential()
для регистрации новых учетных данных, когда пользователь нажимает Добавить учетные данные .
- Добавьте
registerCredential
к существующему операторуimport
.
просмотры/home.html
import { _fetch, registerCredential, unregisterCredential } from '/client.js';
- Вызвать
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
. Если биометрическая аутентификация недоступна или биометрическая аутентификация не удалась, пользовательский интерфейс возвращается к использованию существующей формы пароля.
Создать функцию authenticate()
Создайте функцию с именем authenticate()
, которая проверяет личность пользователя с помощью отпечатка пальца. Вы добавляете код JavaScript здесь:
общедоступный/client.js
export const authenticate = async () => {
};
Получите вызов и другие параметры с конечной точки сервера.
- Перед аутентификацией проверьте, есть ли у пользователя сохраненный идентификатор учетных данных, и установите его в качестве параметра запроса, если он есть.
Когда вы указываете идентификатор учетных данных вместе с другими параметрами, сервер может предоставить соответствующие allowCredentials
и это делает проверку пользователя надежной.
общедоступный/client.js
const opts = {};
let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
url += `?credId=${encodeURIComponent(credId)}`;
}
- Прежде чем просить пользователя пройти аутентификацию, попросите сервер отправить запрос и другие параметры. Вызовите
_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
должен быть либо отдельным объектом в массиве, либо пустым массивом в зависимости от того, найдены ли учетные данные с идентификатором в параметре запроса на стороне сервера.
- Разрешите обещание с
null
значением, когдаallowCredentials
является пустым массивом, чтобы пользовательский интерфейс возвращался к запросу пароля.
if (options.allowCredentials.length === 0) {
console.info('No registered credentials found.');
return Promise.resolve(null);
}
Локально проверьте пользователя и получите учетные данные
- Поскольку эти параметры доставляются закодированными для прохождения через протокол HTTP, преобразуйте некоторые параметры обратно в двоичные, в частности,
challenge
и экземплярыid
, включенные в массивallowCredentials
:
общедоступный/client.js
options.challenge = base64url.decode(options.challenge);
for (let cred of options.allowCredentials) {
cred.id = base64url.decode(cred.id);
}
- Вызовите метод
navigator.credentials.get()
, чтобы проверить личность пользователя с помощью UVPA.
общедоступный/client.js
const cred = await navigator.credentials.get({
publicKey: options
});
Как только пользователь подтвердит свою личность, вы должны получить объект учетных данных, который вы можете отправить на сервер и аутентифицировать пользователя.
Подтвердите учетные данные
Вот пример объекта PublicKeyCredential
( response
— AuthenticatorAssertionResponse
), который вы должны были получить:
{
"id": "...",
"type": "public-key",
"rawId": "...",
"response": {
"clientDataJSON": "...",
"authenticatorData": "...",
"signature": "...",
"userHandle": ""
}
}
- Закодируйте двоичные параметры учетных данных, чтобы их можно было доставить на сервер в виде строки:
общедоступный/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,
};
}
- Отправьте объект на сервер и, если он вернет
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. Включите повторную аутентификацию
Построить пользовательский интерфейс
Когда пользователь возвращается, вы хотите, чтобы он повторно аутентифицировался как можно проще и безопаснее. Вот где сияет биометрическая аутентификация. Однако есть случаи, когда биометрическая аутентификация может не работать:
- УФПА недоступен.
- Пользователь еще не зарегистрировал учетные данные на своем устройстве.
- Хранилище очищено, и устройство больше не помнит идентификатор учетных данных.
- Пользователь не может подтвердить свою личность по какой-либо причине, например, когда его палец мокрый или он носит маску.
Вот почему всегда важно предоставлять другие варианты входа в качестве запасных вариантов. В этой лаборатории кода вы используете решение для пароля на основе формы.
- Добавьте пользовательский интерфейс, чтобы показать кнопку аутентификации, которая вызывает биометрическую аутентификацию в дополнение к форме пароля.
Используйте 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>
- Добавьте
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');
});
Активировать биометрическую аутентификацию
Наконец, включите биометрическую аутентификацию.
- Добавьте
authenticate
к существующему операторуimport
:
просмотры/reauth.html
import { _fetch, authenticate } from '/client.js';
- Вызовите
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. Поздравляем!
Вы закончили эту лабораторную работу!
Учить больше
- Веб-аутентификация: API для доступа к учетным данным открытого ключа уровня 1
- Введение в WebAuthn API
- Семинар FIDO WebAuthn
- Руководство по WebAuthn: DUOSEC
- Ваш первый Android FIDO2 API
Отдельное спасибо Юрию Акерманну из FIDO Alliance за помощь.