Документація · Віджети

Вхід за КЕП (авторизація користувачів)

Користувач підтверджує особу своїм ключем, а ваш бекенд отримує криптографічно перевірену ідентичність: ПІБ, РНОКПП, ЄДРПОУ. Схема 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 і чинність сертифіката.