WebAuthn
Критично важливо: WebAuthn — це не просто ще один спосіб 2FA. Це основа сучасної phishing-resistant автентифікації, яка дозволяє K2 ERP захищати адміністраторів, фінанси, бухгалтерію, HR, керівників і критичні операції краще, ніж SMS, email-коди або звичайні OTP.
Правильне рішення для K2 ERP: реалізувати WebAuthn як технічну основу для FIDO2, passkeys і hardware security keys, щоб забезпечити безпечний вхід, step-up підтвердження критичних дій і захист від phishing.
Орієнтир стандарту: WebAuthn — це W3C Web Authentication API для доступу до public key credentials. FIDO Alliance описує WebAuthn як стандартний web API, вбудований у браузери та платформи для підтримки FIDO Authentication, а CTAP2 дозволяє використовувати зовнішні автентифікатори через USB, NFC або BLE.
1. Що таке WebAuthn
WebAuthn — це стандартний web API, який дозволяє сайту або web-додатку виконувати автентифікацію користувача за допомогою криптографічних ключів замість або на додаток до пароля.
У контексті K2 ERP WebAuthn дозволяє:
- реєструвати passkeys;
- реєструвати hardware security keys;
- входити без пароля або з сильним другим фактором;
- виконувати step-up підтвердження критичних дій;
- захищати користувачів від phishing;
- зберігати в ERP тільки публічний ключ, а не секрет користувача.
WebAuthn працює через об’єкт `PublicKeyCredential`. MDN описує `PublicKeyCredential` як credential для входу в сервіс через стійку до phishing і витоків пару асиметричних ключів — public/private key pair — замість пароля.
2. WebAuthn, FIDO2, CTAP2 і passkeys
| Термін | Що означає | Роль у K2 ERP |
|---|---|---|
| WebAuthn | Browser API для створення та використання public key credentials. | Дозволяє K2 ERP реєструвати ключі та перевіряти вхід. |
| FIDO2 | Набір стандартів для сильної автентифікації. | Загальна технологічна основа WebAuthn + CTAP2. |
| CTAP2 | Протокол взаємодії браузера / ОС із зовнішнім автентифікатором. | Дозволяє використовувати USB/NFC/BLE security key. |
| Passkey | FIDO credential, прив’язаний до акаунта користувача. | Зручний вхід у K2 ERP через пристрій, біометрію або PIN. |
| Security key | Фізичний FIDO2-ключ. | Найкращий варіант для адміністраторів і фінансів. |
| Platform authenticator | Автентифікатор, вбудований у пристрій. | Windows Hello, Touch ID, Face ID, Android passkey. |
| Roaming authenticator | Переносний автентифікатор. | YubiKey, Feitian, Titan Key або інший FIDO2-ключ. |
Passkey — це FIDO-облікові дані, які дозволяють входити в застосунки та сайти так само, як користувач розблоковує пристрій: біометрією, PIN або pattern. Такі credentials прив’язані до акаунта користувача на сайті чи в застосунку.
3. Чому WebAuthn важливий саме для ERP
ERP — це система, де викрадений доступ може призвести не тільки до витоку даних, а й до прямої фінансової шкоди.
У K2 ERP можуть бути:
- фінансові документи;
- банківські виписки;
- платежі;
- зарплати;
- податкові дані;
- договори;
- контрагенти;
- реквізити;
- ціни;
- знижки;
- залишки;
- виробництво;
- управлінська звітність;
- інтеграції з банками, ПРРО, маркетплейсами, доставкою та електронним підписом.
Ризик без WebAuthn: SMS, email-код або TOTP можуть бути введені на фальшивому сайті. WebAuthn перевіряє прив’язку credential до правильного домену, тому фішинговий сайт не зможе використати ключ для входу в справжню K2 ERP.
4. Основні поняття WebAuthn
| Поняття | Опис | Приклад |
|---|---|---|
| Relying Party | Сервіс, який виконує автентифікацію користувача. | K2 ERP. |
| RP ID | Домен, до якого прив’язаний credential. | erp.example.com або example.com. |
| Origin | Повний origin web-додатку. | https://erp.example.com. |
| Challenge | Випадкові дані, які сервер створює для реєстрації або входу. | Base64url random bytes. |
| Credential ID | Ідентифікатор зареєстрованого ключа. | Унікальний ID credential. |
| Public key | Публічний ключ, який зберігається на сервері. | Зберігається в K2 ERP. |
| Private key | Приватний ключ, який залишається в автентифікаторі. | Не передається в K2 ERP. |
| Attestation | Дані про створення credential і автентифікатор. | Опційно використовується під час реєстрації. |
| Assertion | Підпис challenge під час входу. | Перевіряється сервером. |
| User verification | Перевірка користувача на пристрої. | PIN, Touch ID, Face ID, Windows Hello. |
Критично важливо: приватний ключ ніколи не повинен потрапляти на сервер K2 ERP. У K2 ERP зберігаються тільки public key, credential_id, sign_count, metadata і статус credential.
5. Як працює реєстрація WebAuthn
5.1. Бізнес-процес
1. Користувач входить у K2 ERP. 2. Відкриває «Налаштування безпеки». 3. Натискає «Додати passkey / security key». 4. K2 ERP створює registration challenge. 5. Frontend викликає navigator.credentials.create(). 6. Браузер звертається до автентифікатора. 7. Користувач підтверджує дію PIN, біометрією або дотиком до ключа. 8. Автентифікатор створює public/private key pair. 9. Frontend передає attestation response на backend. 10. Backend перевіряє challenge, origin, RP ID і зберігає credential.
5.2. Важливі перевірки під час реєстрації
Backend K2 ERP повинен перевірити:
- challenge збігається з тим, який був створений сервером;
- challenge не прострочений;
- origin дозволений;
- RP ID правильний;
- credential ще не зареєстрований;
- user_id відповідає активному користувачу;
- user verification виконано, якщо це вимагається політикою;
- алгоритм підпису підтримується;
- public key коректно витягнуто та збережено;
- credential_id унікальний.
6. Як працює вхід через WebAuthn
6.1. Бізнес-процес
1. Користувач відкриває K2 ERP. 2. Вводить email або обирає passkey-вхід. 3. K2 ERP створює authentication challenge. 4. Frontend викликає navigator.credentials.get(). 5. Браузер звертається до passkey або security key. 6. Користувач підтверджує вхід на пристрої. 7. Автентифікатор підписує challenge приватним ключем. 8. Frontend передає assertion response на backend. 9. Backend знаходить credential_id і public key. 10. Backend перевіряє підпис, origin, RP ID, challenge і sign_count. 11. Якщо все коректно — створюється сесія K2 ERP.
6.2. Важливі перевірки під час входу
Backend K2 ERP повинен перевірити:
- credential_id існує;
- credential активний;
- credential не відкликаний;
- challenge збігається;
- challenge не прострочений;
- origin дозволений;
- RP ID hash правильний;
- підпис assertion валідний;
- user verification відповідає політиці;
- sign_count не зменшився, якщо автентифікатор його підтримує;
- користувач не заблокований;
- політика ролі дозволяє цей метод входу.
7. RP ID та Origin
RP ID і Origin — критичні для безпеки WebAuthn.
| Параметр | Приклад | Значення |
|---|---|---|
| RP ID | erp.example.com | Домен, до якого прив’язується credential. |
| Origin | https://erp.example.com | Конкретний origin web-додатку. |
| Неправильний origin | https://fake-erp.example.net | Повинен бути відхилений. |
| Dev origin | https://dev-erp.example.com | Має бути окремо дозволений тільки для dev/test. |
MDN зазначає, що public key credential може використовуватись для автентифікації тільки з тим самим relying party, з яким він був зареєстрований, і RP ID має збігатися під час `navigator.credentials.get()`.
Критично важливо: не можна дозволяти wildcard origin або приймати будь-який домен. Для production K2 ERP список дозволених origin має бути жорстко визначений.
8. Де WebAuthn має бути обов’язковим у K2 ERP
| Роль / зона | Чому потрібен WebAuthn | Рівень |
|---|---|---|
| Super Admin | Повний контроль над ERP. | Обов’язково |
| Адміністратор | Ролі, права, налаштування, API token, інтеграції. | Обов’язково |
| Фінансовий директор | Банки, платежі, бюджети, управлінська звітність. | Обов’язково |
| Головний бухгалтер | Податки, зарплати, фінанси, документи. | Обов’язково |
| HR / зарплата | Персональні та зарплатні дані. | Рекомендовано |
| Керівники | Управлінська звітність, фінансові показники. | Рекомендовано |
| Віддалений доступ | Вищий ризик phishing і компрометації. | Рекомендовано |
| Масовий експорт | Ризик витоку даних. | Step-up WebAuthn |
9. Step-up WebAuthn для критичних дій
WebAuthn можна використовувати не тільки для входу, а й для повторного підтвердження небезпечних операцій.
| Операція | Ризик | Контроль |
|---|---|---|
| Зміна банківських реквізитів | Підміна рахунку. | Step-up WebAuthn + погодження. |
| Створення API token | Прихований доступ до ERP. | Step-up WebAuthn + audit log. |
| Зміна ролі користувача | Розширення доступу. | Step-up WebAuthn адміністратора. |
| Масовий експорт клієнтів | Витік бази. | Step-up WebAuthn + alert. |
| Підтвердження платежу | Фінансова втрата. | Step-up WebAuthn + двоетапне погодження. |
| Вимкнення інтеграції | Зупинка бізнес-процесу. | Step-up WebAuthn. |
10. Архітектура WebAuthn у K2 ERP
K2 ERP Frontend
|
| navigator.credentials.create()
| navigator.credentials.get()
v
Browser WebAuthn API
|
v
Platform Authenticator / Security Key
|
v
K2 ERP Backend
|
| verify challenge, origin, rpId, signature
v
Credential Storage + Audit Log
|
v
Session Service / Step-up Action Service
11. API K2 ERP для WebAuthn
11.1. Почати реєстрацію credential
POST /api/v1/security/webauthn/registration/options
11.2. Завершити реєстрацію credential
POST /api/v1/security/webauthn/registration/verify
11.3. Почати автентифікацію
POST /api/v1/security/webauthn/authentication/options
11.4. Завершити автентифікацію
POST /api/v1/security/webauthn/authentication/verify
11.5. Список credential користувача
GET /api/v1/security/webauthn/credentials
11.6. Відкликати credential
POST /api/v1/security/webauthn/credentials/{credential_id}/revoke
11.7. Позначити credential як втрачений
POST /api/v1/security/webauthn/credentials/{credential_id}/mark-lost
11.8. Step-up WebAuthn
POST /api/v1/security/webauthn/step-up/options POST /api/v1/security/webauthn/step-up/verify
12. Модель даних WebAuthn
12.1. webauthn_credentials
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID запису. |
| user_id | uuid | Користувач. |
| credential_id | text | WebAuthn credential ID. |
| public_key | text | Публічний ключ. |
| sign_count | integer | Лічильник підписів, якщо підтримується. |
| rp_id | varchar | RP ID. |
| origin | varchar | Origin реєстрації. |
| authenticator_type | varchar | PLATFORM або ROAMING. |
| transport | varchar | USB, NFC, BLE, INTERNAL. |
| device_name | varchar | Назва пристрою або ключа. |
| aaguid | varchar | Ідентифікатор моделі автентифікатора. |
| backup_eligible | boolean | Чи може credential бути синхронізованим. |
| backup_state | boolean | Чи credential зараз у backup-синхронізації. |
| user_verified_required | boolean | Чи вимагалась user verification. |
| status | varchar | ACTIVE, LOST, DISABLED, REVOKED. |
| last_used_at | timestamp | Останнє використання. |
| created_at | timestamp | Дата створення. |
12.2. webauthn_challenges
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID challenge. |
| user_id | uuid | Користувач. |
| challenge_type | varchar | REGISTRATION, LOGIN, STEP_UP, RECOVERY. |
| challenge | text | Випадковий challenge. |
| rp_id | varchar | RP ID. |
| origin | varchar | Дозволений origin. |
| status | varchar | PENDING, PASSED, FAILED, EXPIRED. |
| expires_at | timestamp | Строк дії. |
| created_at | timestamp | Дата створення. |
12.3. webauthn_events
| Поле | Тип | Опис |
|---|---|---|
| id | uuid | ID події. |
| user_id | uuid | Користувач. |
| credential_record_id | uuid | Credential, якщо є. |
| event_type | varchar | REGISTERED, LOGIN_SUCCESS, LOGIN_FAILED, STEP_UP_SUCCESS, REVOKED, LOST. |
| ip_address | varchar | IP. |
| user_agent | text | Браузер / пристрій. |
| risk_score | integer | Оцінка ризику. |
| payload | jsonb | Технічні дані без секретів. |
| created_at | timestamp | Дата. |
13. Приклад frontend-логіки
13.1. Реєстрація passkey / security key
async function registerWebAuthnCredential() {
const optionsResponse = await fetch("/api/v1/security/webauthn/registration/options", {
method: "POST",
headers: { "Content-Type": "application/json" }
});
const options = await optionsResponse.json();
const credential = await navigator.credentials.create({
publicKey: options.publicKey
});
const verifyResponse = await fetch("/api/v1/security/webauthn/registration/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(credential)
});
return await verifyResponse.json();
}
13.2. Вхід через WebAuthn
async function loginWithWebAuthn(email) {
const optionsResponse = await fetch("/api/v1/security/webauthn/authentication/options", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email })
});
const options = await optionsResponse.json();
const assertion = await navigator.credentials.get({
publicKey: options.publicKey
});
const verifyResponse = await fetch("/api/v1/security/webauthn/authentication/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(assertion)
});
return await verifyResponse.json();
}
14. Приклад Python-логіки
14.1. Перевірка, чи потрібен WebAuthn
def is_webauthn_required(user: "User", action: str | None = None, context: dict | None = None) -> bool:
if user.role in ["SUPER_ADMIN", "ADMIN", "CFO", "CHIEF_ACCOUNTANT"]:
return True
if action in [
"CHANGE_BANK_DETAILS",
"CREATE_API_TOKEN",
"CHANGE_USER_ROLE",
"MASS_EXPORT",
"APPROVE_PAYMENT",
]:
return True
if context and context.get("remote_access") is True:
return user.security_policy.require_webauthn_for_remote_access
return False
14.2. Створення registration options
def create_webauthn_registration_options(user: "User", db: "Session") -> dict:
challenge = webauthn_service.generate_challenge()
webauthn_challenge_repository.create(
db=db,
data={
"user_id": user.id,
"challenge_type": "REGISTRATION",
"challenge": challenge,
"rp_id": "erp.example.com",
"origin": "https://erp.example.com",
"status": "PENDING",
"expires_at": utc_now_plus_minutes(5),
},
)
return {
"publicKey": {
"rp": {
"name": "K2 ERP",
"id": "erp.example.com",
},
"user": {
"id": str(user.id),
"name": user.email,
"displayName": user.full_name,
},
"challenge": challenge,
"pubKeyCredParams": [
{"type": "public-key", "alg": -7},
{"type": "public-key", "alg": -257}
],
"authenticatorSelection": {
"userVerification": "required",
"residentKey": "preferred"
},
"timeout": 300000,
"attestation": "none"
}
}
14.3. Збереження credential
def save_webauthn_credential(user_id: str, verification_result: dict, db: "Session") -> "WebAuthnCredential":
credential = webauthn_credential_repository.create(
db=db,
data={
"user_id": user_id,
"credential_id": verification_result["credential_id"],
"public_key": verification_result["public_key"],
"sign_count": verification_result.get("sign_count", 0),
"rp_id": verification_result.get("rp_id"),
"origin": verification_result.get("origin"),
"authenticator_type": verification_result.get("authenticator_type"),
"transport": verification_result.get("transport"),
"aaguid": verification_result.get("aaguid"),
"backup_eligible": verification_result.get("backup_eligible"),
"backup_state": verification_result.get("backup_state"),
"user_verified_required": True,
"status": "ACTIVE",
},
)
audit_logger.log(
user_id=user_id,
event_type="WEBAUTHN_CREDENTIAL_REGISTERED",
payload={
"credential_record_id": str(credential.id),
"authenticator_type": credential.authenticator_type,
},
)
db.commit()
return credential
14.4. Перевірка входу
def verify_webauthn_login(user: "User", assertion_response: dict, db: "Session") -> bool:
credential = webauthn_credential_repository.get_by_credential_id(
db=db,
credential_id=assertion_response["credential_id"],
)
if not credential or credential.status != "ACTIVE":
return False
is_valid = webauthn_service.verify_assertion(
assertion_response=assertion_response,
public_key=credential.public_key,
expected_rp_id="erp.example.com",
expected_origin="https://erp.example.com",
require_user_verification=True,
)
if not is_valid:
audit_logger.log(
user_id=user.id,
event_type="WEBAUTHN_LOGIN_FAILED",
payload={"credential_record_id": str(credential.id)},
)
db.commit()
return False
credential.last_used_at = utc_now()
audit_logger.log(
user_id=user.id,
event_type="WEBAUTHN_LOGIN_SUCCESS",
payload={"credential_record_id": str(credential.id)},
)
db.commit()
return True
15. Recovery та втрата credential
Користувач може втратити пристрій, security key або доступ до passkey. Тому recovery має бути контрольованим.
Потрібно передбачити:
- мінімум два credentials для адміністраторів;
- резервний hardware security key;
- recovery codes;
- заявку на відновлення;
- перевірку особи користувача;
- погодження другим адміністратором;
- журнал recovery;
- відкликання втраченого credential;
- обов’язкову реєстрацію нового credential;
- тимчасовий доступ із коротким TTL.
Критично важливо: recovery-процедура не повинна обходити WebAuthn без контролю. Якщо адміністратор може просто вимкнути WebAuthn для себе без погодження, це критична вразливість.
16. Dashboard WebAuthn
16.1. KPI адміністратора безпеки
| Показник | Значення | Стан |
|---|---|---|
| Super Admin із WebAuthn | 100% | Норма |
| Адміністраторів із WebAuthn | 100% | Норма |
| Фінансових ролей із WebAuthn | 82% | Потрібна дія |
| Бухгалтерів із WebAuthn | 76% | Потрібна дія |
| Користувачів тільки з SMS | 18 | Замінити |
| Втрачених credentials | 2 | Контроль |
| Credentials без використання понад 90 днів | 11 | Перевірити |
| Підозрілих WebAuthn-помилок | 3 | Критично |
16.2. Проблемні користувачі
| Користувач | Роль | Поточний метод | Ризик | Дія |
|---|---|---|---|---|
| admin2 | Адміністратор | TOTP без WebAuthn | Критично | Видати security key |
| buh_olena | Бухгалтер | SMS | Високий | Перевести на WebAuthn або TOTP |
| cfo | Фіндиректор | WebAuthn security key | Добре | Без дії |
| director | Керівник | Passkey | Добре | Без дії |
17. План впровадження WebAuthn у K2 ERP
Етап 1. Аудит доступів
- знайти всіх адміністраторів;
- знайти користувачів фінансових ролей;
- знайти бухгалтерію;
- знайти HR / зарплату;
- знайти керівників;
- знайти користувачів із віддаленим доступом;
- знайти користувачів тільки з SMS;
- знайти користувачів без MFA.
Етап 2. Політика WebAuthn
- визначити ролі, для яких WebAuthn обов’язковий;
- визначити дозволені типи credential;
- визначити, чи дозволені synced passkeys;
- визначити, чи потрібні hardware keys для admin;
- визначити recovery-процедуру;
- визначити step-up WebAuthn для критичних дій.
Етап 3. Технічна реалізація
- WebAuthn registration;
- WebAuthn authentication;
- credential storage;
- challenge storage;
- RP ID validation;
- origin validation;
- sign_count validation;
- credential revocation;
- lost credential flow;
- WebAuthn events;
- dashboard.
Етап 4. Пілот
- super admin;
- адміністратори;
- фінансовий директор;
- головний бухгалтер;
- 5–10 power users;
- перевірка recovery;
- перевірка step-up дій.
Етап 5. Масове впровадження
- бухгалтерія;
- фінанси;
- HR;
- керівники;
- віддалені користувачі;
- поступова відмова від SMS.
18. Acceptance Criteria
18.1. Реєстрація WebAuthn
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-1 | Користувач додає passkey. | Credential реєструється, public key зберігається. |
| AC-2 | Користувач додає hardware security key. | Credential реєструється як roaming authenticator. |
| AC-3 | Challenge прострочений. | Реєстрація не приймається. |
| AC-4 | Origin неправильний. | Реєстрація відхиляється. |
| AC-5 | RP ID неправильний. | Реєстрація відхиляється. |
18.2. Вхід через WebAuthn
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-6 | Користувач входить через passkey. | Сесія створюється після успішної перевірки assertion. |
| AC-7 | Користувач використовує невідомий credential. | Вхід відхиляється. |
| AC-8 | Credential відкликаний. | Вхід відхиляється. |
| AC-9 | Admin не має WebAuthn. | Вхід блокується або вимагається реєстрація WebAuthn. |
18.3. Step-up WebAuthn
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-10 | Користувач змінює банківські реквізити. | Система вимагає step-up WebAuthn. |
| AC-11 | Адміністратор створює API token. | Система вимагає step-up WebAuthn. |
| AC-12 | Користувач запускає масовий експорт. | Система вимагає step-up WebAuthn і логування. |
18.4. Recovery
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-13 | Користувач повідомляє про втрату ключа. | Credential позначається LOST або REVOKED. |
| AC-14 | Admin recovery. | Потрібне погодження другим адміністратором. |
| AC-15 | Recovery завершено. | Подія логуються, користувач реєструє новий credential. |
18.5. Dashboard
| № | Критерій | Очікуваний результат |
|---|---|---|
| AC-16 | Адміністратор відкриває dashboard WebAuthn. | Він бачить покриття WebAuthn по ролях. |
| AC-17 | Є admin без WebAuthn. | Він підсвічується червоним. |
| AC-18 | Є користувач тільки з SMS. | Він підсвічується червоним або помаранчевим. |
| AC-19 | Є втрачений credential. | Він показується в контрольному списку. |
19. Висновок
WebAuthn — це технічна основа сучасного безпечного входу в ERP.
Червона зона: адміністратори, фінанси, бухгалтерія або керівники входять у ERP через пароль, SMS або email-код без WebAuthn.
Зелена зона: K2 ERP використовує WebAuthn / passkeys / security keys для критичних ролей, step-up WebAuthn для небезпечних операцій, dashboard покриття credentials, журнал подій і контроль recovery.
Головне правило: для ERP, яка керує фінансами, зарплатами, договорами, складом і управлінською звітністю, WebAuthn має стати стандартом для критичних ролей.
20. Джерела
- W3C WebAuthn Level 3.
- W3C WebAuthn Level 2.
- FIDO Alliance: User Authentication Specifications Overview.
- FIDO Alliance: Passkeys.
- MDN WebAuthn API.
- MDN PublicKeyCredential.
- Внутрішні вимоги K2 ERP до доступів, MFA, WebAuthn, журналювання та критичних дій.