OAuth2: де ламається вхід і чому це дорожче, ніж здається
OAuth2 майже ніколи не ламається «на вході» — він ламається на деталях, які не видно в happy path.
Класика: публічний клієнт без PKCE, і код авторизації йде у відкритому вигляді через перехоплення редиректу — на мобільному пристрої або в браузері його знімає будь-який застосунок, що зареєстрував ту саму URI-схему.
Далі зловмисник обмінює код на токен раніше за легітимний клієнт, і в журналах це виглядає як звичайний успішний вхід.
Друга міна — вічні токени.
Access-токен без короткого терміну життя і refresh без ротації живе до ручного відкликання: витік із логів, із пам'яті пристрою або через сторонній скрипт дає постійний доступ, а інвалідувати його нічим — немає ні списку відкликання, ні прив'язки до пристрою. Відкликання доводиться робити зміною секретів, що валить усіх користувачів одразу.
Третій сценарій — розсинхрон сесій між сервісами. Користувач увійшов в одному застосунку, а сусідній сервіс його не бачить: різні видавці токенів, незбіжні аудиторії, роз'їхалися ключі підпису. Зовні це «відмова входу» без виразної помилки, і підтримка розбирає його наосліп.
Окремо — сплески відмов при оновленні токена. Коли refresh-запити йдуть паралельно з кількох вкладок або реплік, ротація спрацьовує на першому запиті, а решта отримують відмову і розлогінюють користувача. Пік таких збоїв припадає на ранок і на моменти деплою, коли сесії масово перезапитують доступ.
Ціна в усіх чотирьох сценаріїв одна: втрачені замовлення, навантаження на підтримку і репутаційні втрати, яких не видно в метриках доступності. Розбираємо, як це закривається.
Схема входу: PKCE, ротація токенів, вхід через соцмережі
PKCE замість implicit flow
Класичний implicit flow віддає токен прямо в адресний рядок — він осідає в історії браузера, логах проксі та заголовку Referer.
PKCE закриває цей клас: клієнт генерує випадковий code_verifier, надсилає його хеш (code_challenge) на авторизацію, а провайдер звіряє пару під час обміну коду на токен. Перехоплений код без verifier марний — атака на редирект перестає працювати в принципі.
Ротація токенів і строк життя сесії
Refresh-токен одноразовий: під час обміну видається нова пара, стара інвалідується. Якщо той самий refresh надходить повторно — це ознака крадіжки, і весь ланцюжок сесій користувача відкликається автоматично.
Access-токен живе хвилини, тому компрометація дає вікно в одиниці хвилин, а не дні. Строк життя сесії задається політикою: для адміністративних розділів — години, для звичайних — дні з продовженням за активністю.
Вхід через соцмережі та відкликання доступу
Зовнішній провайдер повертає підписаний ідентифікатор, за яким ми знаходимо або створюємо локальний обліковий запис — прив'язка йде за стабільним ідентифікатором, а не за поштою, тому зміна адреси не породжує дубль.
Відкликання доступу миттєве: запис у списку токенів, що відкликаються, перевіряється на кожному запиті, і сесія вмирає в межах кешу перевірки.
| Варіант | Коли підходить | Обмеження |
|---|---|---|
| PKCE + ротація | Публічні клієнти, мобільні та SPA | Потрібен серверний обмін коду |
| Класичний серверний потік | Конфіденційні клієнти з секретом | Секрет витікає разом із бекендом |
| Довгоживучий токен | Внутрішні сервіси без користувача | Відкликання запізнюється, вікно атаки зростає |
Схему збираємо на Keycloak або на вбудованому провайдері — вибір залежить від того, чи потрібен єдиний вхід у сторонні системи.
Порядок робіт: від аудиту поточного входу до передачі налаштувань
Роботи виконуються послідовно: кожен наступний етап спирається на артефакти попереднього, тому проміжні рішення не доводиться переглядати на середині. Нижче — як влаштований процес від аудиту входу до передачі налаштувань.
-
Аудит поточного входу. Розбираємо, як застосунок видає та зберігає токени, де живуть сесії, які провайдери вже підключені та які потоки OAuth2 використовуються.
Фіксуємо слабкі місця: довгоживучі refresh-токени без ротації, відсутність PKCE для публічних клієнтів, токени в localStorage, відсутність відкликання при виході.
-
Проєктування потоків. Обираємо Authorization Code + PKCE для клієнтів, які не можуть зберігати секрет, і визначаємо терміни життя access- та refresh-токенів.
Проєктуємо ротацію refresh-токенів із виявленням повторного використання: при спробі застосувати вже відкликаний токен весь ланцюжок інвалідується.
-
Налаштування провайдерів і політик. Реєструємо застосунок у провайдерів, налаштовуємо redirect-адреси, scopes та правила зіставлення облікових записів при social login. Прописуємо політики відкликання, зберігання та прив'язки кількох способів входу до одного користувача.
-
Перевірка сценаріїв відмови. Прогоняємо те, що зазвичай ламається в проді: прострочений refresh-токен, відкликання доступу на боці провайдера, розсинхрон годинників, повторний колбек, конфлікт облікових записів при вході через різні сервіси. Кожен сценарій має завершуватися передбачуваним станом, а не завислою сесією.
-
Передача налаштувань. Віддаємо конфігурації провайдерів, схеми потоків, політики токенів і документацію з експлуатації — щоб команда замовника могла змінювати параметри без нас.
Опишіть вашу поточну схему входу — повернемося з оцінкою та планом робіт.
Чим відрізняється код авторизації з PKCE від неявного потоку?
Неявний потік (implicit flow) був компромісом епохи, коли браузери не вміли надійно зберігати секрети, а сервери авторизації не підтримували обмін коду на токен без участі бекенду.
У ньому клієнтський застосунок отримує access token прямо у фрагменті адресного рядка — минаючи проміжний крок із кодом.
Це скорочувало кількість запитів, але ціна виявилася високою: токен осідав в історії браузера, логах проксі та referer-заголовках, а перехопити його через упроваджений скрипт або підмінений розширення не становило труднощів.
Саме тому OAuth2 в актуальній редакції прямо не рекомендує неявний потік для клієнтських застосунків.
Код авторизації з PKCE вирішує те саме завдання інакше. Застосунок генерує випадкове значення (code_verifier), обчислює з нього похідну (code_challenge) і надсилає її разом із запитом на вхід.
Сервер повертає одноразовий код, а обмін коду на токен можливий лише за пред'явлення вихідного verifier. Навіть якщо зловмисник перехопить код — через редирект, логи або шкідливий застосунок на пристрої, — без verifier він марний.
Це закриває клас атак із перехопленням коду авторизації, який у неявному потоці просто був відсутній як етап.
Ключова відмінність — де опиняється токен. У неявному потоці він одразу потрапляє в клієнтське середовище й живе там до закінчення терміну. У потоці з PKCE токен видається лише після перевірки verifier, а сам код одноразовий і прив'язаний до конкретного запиту.
Для мобільних і односторінкових застосунків, де немає довіреного бекенду, це єдиний коректний варіант: секрет клієнта там зберігати ніде, а PKCE замінює його криптографічною прив'язкою.
Є й друга лінія захисту — refresh rotation. Довгоживучий refresh token при кожному оновленні замінюється новим, а старий інвалідується. Якщо токен украли й використали, легітимний клієнт при наступному оновленні отримає відмову, і весь ланцюжок сесій відкликається.
У неявному потоці refresh token взагалі не видавався — продовжити сесію можна було лише повторним входом, що штовхало розробників до подовження терміну життя access token і зростання вікна уразливості.
На практиці вибір виглядає так: для серверних застосунків із довіреним бекендом — код авторизації із секретом клієнта; для односторінкових і мобільних — код авторизації з PKCE; неявний потік залишається лише в успадкованих системах, де його заміна потребує перезбирання клієнта.
Безпека входу тут вимірюється не зручністю інтеграції, а тим, скільки класів атак закрито на рівні протоколу, а не на рівні дисципліни розробника.
Кейс: вхід перестав відвалюватися після ротації токенів
Клієнт — сервіс з особистим кабінетом і мобільним застосунком — звертався зі скаргою, яку підтримка не могла закрити: користувачі регулярно скаржилися, що «вхід злітає».
Частка невдалих входів трималася на рівні 8%: частина запитів на оновлення токена надходила з уже використаним значенням, сервер відповідав відмовою, і людина бачила форму логіна замість кабінету.
Окремий біль — відкликання доступу: після блокування співробітника його сесія жила до закінчення терміну дії токена, тобто доступ зберігався годинами.
Що змінили
Розібрали потік автентифікації покроково і знайшли корінь: застосунок оновлював токен паралельно з кількох вкладок і фонових задач, а старий токен при цьому не інвалідовувався атомарно.
Запровадили ротацію токенів оновлення: кожен обмін видає нову пару, попереднє значення позначається використаним, а повторне пред'явлення трактується як ознака витоку — весь ланцюжок сесій користувача відкликається.
Гонку між вкладками закрили на стороні клієнта: обмін іде через єдину точку з блокуванням, інші запити чекають результату, а не ініціюють свій.
Для входу через зовнішніх провайдерів підключили PKCE — код авторизації більше не проходить у відкритому вигляді, перехоплений редірект без верифікатора марний.
Відкликання доступу перевели на серверний реєстр активних сесій: адміністратор закриває доступ, і всі пов'язані токени стають недійсними в момент відкликання, а не після закінчення терміну.
Що стало
| Метрика | До | Після |
|---|---|---|
| Частка невдалих входів | ~8% | менше 0,5% |
| Час відкликання доступу | до терміну життя токена | секунди після відкликання |
| Звернення до підтримки щодо скидання сесій | регулярний потік | поодинокі випадки |
Окремо закрили сценарій зміни пароля: він тепер завершує всі інші сесії, крім поточної, — раніше користувач змінював пароль і не розумів, чому стара сесія на іншому пристрої продовжує працювати.
Логіку будували на стандартному протоколі авторизації OAuth 2.0 з розширеннями PKCE і ротацією токенів оновлення — без саморобних схем, які потім важко підтримувати і перевіряти на безпеку.
Сумніви перед впровадженням: сумісність, міграція, відкат
Впровадження OAuth2 майже ніколи не починається з нуля: у замовника вже є база користувачів, власні сесії, ролі та зовнішні сервіси, які звертаються до застосунку за старими правилами.
Тому перше питання — не «як налаштувати провайдера», а «що буде з поточними входами, поки ми переводимо систему на новий контур».
Ми проєктуємо міграцію входу так, щоб старий і новий механізм працювали паралельно: користувач потрапляє в систему будь-яким звичним способом, а обліковий запис поступово прив'язується до нового ідентифікатора.
Окремо фіксуємо план відкату — якщо на проді виявиться несумісність, повернення до попередньої схеми займає хвилини, а не години.
Що якщо у нас нестандартний провайдер або власна база користувачів? OAuth2 розширюємо через власний провайдер ідентифікації: він віддає ті самі дані, що й зовнішні, і підключається до наявної бази без її перебудови. Що якщо частина сервісів не вміє працювати з токенами?
Для них залишаємо сумісний шар доступу, який приймає попередній формат і всередині перевіряє новий — сервіс не переписується повністю.
Чи можна відкотитися, якщо новий вхід зламає частину сценаріїв?
Так. Ми тримаємо обидва контури активними і перемикаємо трафік за часткою користувачів: у разі проблеми повертаємо попередній вхід одним налаштуванням, не відкочуючи реліз повністю. Усі видані токени при цьому залишаються валідними, сесії не розриваються.
На руки замовник отримує не «працює і гаразд», а комплект, за яким систему можна супроводжувати й передати іншій команді:
- конфігурації провайдерів — які параметри задані й чому;
- схеми потоків входу: де видається токен, де перевіряється, де оновлюється;
- політики строків життя токенів і правила їх відкликання;
- матрицю доступів: хто і до яких даних отримує доступ після входу;
- документацію щодо підключення нових сервісів і зовнішніх провайдерів;
- план відкату з точками перемикання та критеріями спрацювання;
- інструкцію для підтримки: як читати логи входу та знаходити причину відмови.
Опишіть вашу поточну схему входу та список сервісів, які до неї підключені, — повернемося з оцінкою та планом переведення без зупинки роботи.
Терміни етапів впровадження OAuth2 без прив'язки до дат
Впровадження OAuth2 розбивається на етапи з різною тривалістю — від швидких перевірок до робіт, де основний час іде на узгодження сценаріїв і тестування.
Нижче — орієнтовні терміни для типового проєкту: вони залежать від кількості зовнішніх провайдерів, кількості ролей і того, чи вже є готова інфраструктура автентифікації.
Найдовше триває інтеграція — саме там випливають розбіжності між моделлю користувачів у провайдера та вашою внутрішньою структурою ролей.
Перевірка займає окремий блок: сценарії із закінченням токена й одночасним оновленням з кількох вкладок ламаються найчастіше, і їх краще закрити до передачі.
Чи можна прискорити? Так, якщо зовнішній провайдер один і ролі вже описані — етап проєктування скорочується вдвічі. Якщо провайдерів кілька і потрібен єдиний обліковий запис, терміни зростають пропорційно до кількості інтеграцій.
Що якщо частину робіт уже зроблено? Аудит покаже, що перевикористаємо, а що доведеться переписати — від цього залежить, стартуємо ми з проєктування чи із середини налаштування.
Опишіть вашу задачу: скільки точок входу, які провайдери, чи є поточна схема сесій — повернемося з оцінкою та планом за етапами.







