Користувач підтверджує особу своїм ключем, а ваш бекенд отримує криптографічно перевірену ідентичність: ПІБ, РНОКПП, ЄДРПОУ. Схема challenge–response — підпис неможливо підробити чи використати повторно.
Чому не JS-бібліотека
Авторизація за КЕП, де підпис перевіряється JavaScript-бібліотекою в браузері, — не production-безпечне рішення:
- результат перевірки формується на боці клієнта — атакуючий контролює браузер і може підмінити «підпис дійсний» без жодного ключа;
- у відкритих чисто-JS реалізаціях ДСТУ відомі класи вразливостей:
timing-атаки (криптографія без константного часу) і слабка генерація
випадкового
k, що уможливлює відновлення приватного ключа з підписів.
DSTUcrypt закриває обидві проблеми: підпис формується в ізольованому iframe на нашому origin (WASM, константний час), а перевірка виконується на нашому сервері нативним криптоядром. Браузеру не довіряється нічого, крім самого підпису.
Схема (4 кроки)
ваш бекенд ──1─▶ POST /api/auth/challenge ─▶ { challenge }
ваша сторінка ─2─▶ auth-віджет: користувач підписує challenge ключем
ваша сторінка ─3─▶ signature → ваш бекенд
ваш бекенд ──4─▶ POST /api/auth/verify {challenge, signature}
─▶ { ok, subject{…} }
1. Бекенд бере одноразовий challenge
curl -X POST https://dstucrypt.com.ua/api/auth/challenge
# → { "challenge": "dstucrypt-login-v1:…", "expiresIn": 300 }
Challenge живе 5 хвилин і згорає після першої перевірки (захист від повторного використання).
2–3. Сторінка: віджет підписує challenge
import { embed } from 'https://dstucrypt.io/embed/dstucrypt-embed.mjs';
const auth = await embed('auth', { mount: 'modal' });
const { signature, subject, accredited } = await auth.login({ challenge });
// subject/accredited тут — ЛИШЕ для UX (показати «Вітаємо, Іване!» / попередити,
// що ключ не КЕП); довіряти можна тільки відповіді /api/auth/verify (крок 4)
await fetch('/api/my-backend/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ challenge, signature }), // signature — base64-рядок
});
4. Бекенд перевіряє підпис у нас
curl -X POST https://dstucrypt.com.ua/api/auth/verify \
-H "Content-Type: application/json" \
-d '{ "challenge": "dstucrypt-login-v1:…", "signature": "<base64>" }'
Успіх:
{
"ok": true,
"subject": {
"fullName": "Іваненко Іван Іванович",
"taxId": "1234567890",
"orgCode": "12345678",
"issuer": "АЦСК …",
"certSerial": "…",
"validFrom": "2025-01-01 00:00:00",
"validTo": "2027-01-01 00:00:00"
},
"signatureFormat": "CAdES-BES",
"signingTime": "2026-07-31 10:00:00",
"status": "TOTAL-VALID",
"accredited": true, // сертифікат від акредитованого АЦСК
"qualified": true // це вхід за КЕП (акредитований + дійсний)
}
Створіть власну сесію для користувача за subject.taxId (РНОКПП) — це
стабільний ідентифікатор особи. qualified: true означає вхід саме за КЕП;
якщо для вашого сценарію потрібен лише КЕП — відхиляйте qualified: false.
Відмови
| HTTP | reason |
Причина |
|---|---|---|
| 401 | challenge_invalid_or_used |
challenge невідомий, прострочений або вже використаний |
| 200 | signature_invalid (ok:false) |
підпис не пройшов криптоперевірку, вміст ≠ challenge, або сертифікат нечинний |
| 400 | bad_request / bad_signature_encoding |
некоректне тіло запиту |
Вхід через застосунок: Дія.Підпис і Smart ID
Той самий віджет уміє підтверджувати особу ключем із застосунку — Дія або
Приват24. Схема не змінюється: ті самі чотири кроки, та сама перевірка на
/api/auth/verify. Змінюється лише те, чим користувач підписує challenge.
const auth = await embed('auth', { providers: ['file', 'diia', 'smartid'] });
const res = await auth.login({ challenge });
// Для Дії challenge задає НАШ сервер — беремо той, яким реально підписали
await fetch('/api/my-backend/login', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
challenge: res.challenge || challenge,
signature: res.signature,
}),
});
Одна особливість Дії
Дія вимагає, щоб ідентифікатор запиту був гешем ГОСТ 34.311 від рядка
uuid v4. Тому для неї challenge генерує наш сервер, а не ваш бекенд — і
повертає його у res.challenge. Підписаним вмістом стає сам рядок uuid, тож
/api/auth/verify перевіряє його звичайним шляхом, без жодних відмінностей.
Що з цього випливає для вашого коду: беріть res.challenge || challenge.
Для файлового ключа й Smart ID поле буде порожнє, і піде ваш власний challenge;
для Дії — той, який справді підписали. Один рядок покриває всі три способи.
Smart ID такої особливості не має: він підписує ваш challenge як є, а в застосунку показує тексти про вхід, а не про підписання документа.
Коли спосіб один
Якщо ви передали єдиний хмарний спосіб, віджет не показує проміжного екрана з кнопкою «Увійти» — одразу відкриває QR. Вибирати нема з чого, а згоду користувач однаково дає в застосунку.
Для файлового ключа так не робиться: там ще треба обрати файл і ввести пароль, та й саме натискання є свідомою згодою на вхід.
Що взяти до уваги
- Вхід через надавача, як і хмарний підпис, витрачає наш партнерський акаунт — тому доступний оплаченим доменам (див. Дія.Підпис і Smart ID).
- QR живе 3 хвилини й одноразовий.
subjectіз віджета для хмарного способу порожній: дані підписанта віддає/api/auth/verify— авторитетно, з перевіркою ланцюжка. Саме на них і покладайтеся.
Правила довіри
- Довіряйте лише відповіді
POST /api/auth/verify(server-to-server). subjectіз віджета — тільки для UX.- Перевірка виконується нативним криптоядром на нашому сервері: підпис, цілісність, збіг вмісту з challenge і чинність сертифіката.