Авторизація через Дію: як ми інтегрували національну цифрову ідентифікацію в юридичну платформу
Паспорт у смартфоні — тепер ключ до юридичного AI. Ми інтегрували Дія.Підпис для авторизації: deep link на мобільному, QR-код на десктопі, ECDSA + SHA256 для хешування, і юрист підтверджує особу тим самим додатком, яким показує документи на блокпості. Без паролів. Без реєстрації. Один тап — і ви в системі.
Авторизація через Дію: як ми інтегрували національну цифрову ідентифікацію
Паспорт у смартфоні — тепер ключ до юридичного AI.
Чому Дія, а не ще один OAuth
Юридична платформа працює з конфіденційними даними. Google OAuth підтверджує, що ви маєте Gmail. Дія підтверджує, що ви — це ви. Різниця принципова: Дія прив'язана до реального документа — паспорта, ID-картки, або кваліфікованого електронного підпису.
Для юридичної платформи, де адвокатська таємниця та ідентифікація сторін — не опція, а вимога закону, це єдиний правильний рівень верифікації.
Архітектура: два потоки
Мобільний (deep link)
- Користувач натискає "Увійти через Дію"
- Бекенд генерує
requestId(ECDSA + SHA256, base64) - Відкривається deep link
diia://з параметрами сесії - Додаток Дія показує запит на авторизацію
- Користувач підтверджує → Дія надсилає callback з даними
- Бекенд верифікує підпис, створює JWT-сесію
Десктоп (QR-код)
- Бекенд запитує сесію у Дія API (
api2s.diia.gov.ua) - Отримує deep link → конвертує в QR-код
- Користувач сканує QR додатком Дія на телефоні
- Далі — той самий потік: підтвердження → callback → JWT
Криптографія: чому ECDSA
Дія API вимагає хешування requestId через ECDSA з SHA256. Не HMAC, не RSA — саме ECDSA. Це стандарт електронного підпису в Україні (ДСТУ 4145), і Дія слідує йому.
requestId = base64(ECDSA_SHA256(branchId + offerId + requestId))
Кожен запит унікальний. Кожен підпис верифікований. Replay-атаки неможливі.
Що отримуємо від Дія
Після успішної авторизації:
| Поле | Опис |
|---|---|
| ПІБ | Прізвище, ім'я, по батькові |
| Дата народження | З документа |
| ІПН | Індивідуальний податковий номер |
| Серія/номер документа | Паспорт або ID-картка |
| Фото | З документа (опціонально) |
Цього достатньо для повної ідентифікації на юридичній платформі — і для майбутньої інтеграції з ЄРАУ (верифікація адвоката за ІПН).
Безпека
- Дані не зберігаються на стороні Дії — після передачі callback сесія знищується
- Токен сесії одноразовий — повторне використання неможливе
- JWT з коротким TTL — 24 години, refresh через повторну авторизацію
- Basic Auth для API — комунікація бекенд ↔ Дія захищена окремими credentials
UX: один тап замість форми
На мобільному:
- Натиснув "Увійти через Дію" → відкрився додаток → підтвердив → повернувся в LEX AI авторизованим
На десктопі:
- Побачив QR-код → навів камеру → підтвердив у додатку → сторінка автоматично оновилась
Жодних паролів. Жодних форм реєстрації. Жодних "підтвердіть email". Той самий додаток, яким ви показуєте права на блокпості — тепер ваш ключ до юридичного AI.
Три методи авторизації
LEX AI тепер підтримує три незалежні методи входу:
| Метод | Рівень довіри | Найкраще для |
|---|---|---|
| Google OAuth | Базовий | Швидкий старт, ознайомлення |
| Authentik SSO | Корпоративний | Юридичні фірми, організації |
| Дія | Державний | Повна ідентифікація, адвокати |
Юрист обирає свій рівень. Платформа адаптується.
Production post-mortem: Redis + nginx
Після деплою на продакшн за AWS Application Load Balancer авторизація через Дія перестала працювати. Повністю. Користувачі натискали "Увійти через Дію" — і отримували помилку.
Причин виявилось дві, і обидві — інфраструктурні.
Перша: розбіжність ключів у Redis. При ініціації сесії Дія ми записували стейт із одним префіксом, а при зворотному виклику читали з іншим. Redis мовчки повертав null, бекенд вважав сесію невалідною і відхиляв callback. Фікс — уніфікація префіксів ключів в одному місці.
Друга: nginx перезаписував X-Forwarded-Proto. ALB коректно передавав https, але nginx у своїй конфігурації примусово ставив http. Callback URL формувався з HTTP-схемою, Дія відхиляла його як невідповідний зареєстрованому redirect URI. Рішення — nginx тепер пропускає оригінальний заголовок від балансера, а не підставляє свій.
Обидві проблеми не відтворювались локально, бо в dev-середовищі немає ALB і Redis-префікси збігались випадково. Це нагадування: staging має максимально повторювати продакшн.