vyvchy
    Теми розділу

    11 · Security для QA

    Сесії та токени: cookie, JWT, OAuth

    Зміст

    Форма логіну — двері, які застосунок відчиняє один раз. Далі вас упізнають не за паролем, а за одним рядком: значенням сесійної кукі або токеном у заголовку. Тому інциденти навколо доступу трапляються не на формі, а після неї, і тому питання «як ви тестуєте logout?» на співбесіді показове: з відповіді одразу видно, чи бачить кандидат серверний бік сесії.

    Глава змішаного рівня. Перші пʼять розділів базові; розділи з позначкою поглиблення (JWT, , OAuth 2.0 і ) — senior-матеріал, який можна пропустити при першому проході й повернутися перед співбесідою на middle+. Механіка самої кукі й серверної сесії тут не переказується: її дім — глава Кукі, сесії та сховище браузера, а формат JWT і потік OAuth — глава Автентифікація та авторизація.

    Життєвий цикл сесії: чотири моменти, у яких вона ламається

    Веб-сесія (web session) — послідовність HTTP-запитів і відповідей, повʼязаних з одним користувачем. Ставку OWASP формулює без помʼякшень: ідентифікатор автентифікованої сесії тимчасово еквівалентний найсильнішому методу автентифікації застосунку, тобто його досить, щоб обійти і пароль, і другий фактор. Ведуть до захоплення пʼять шляхів: розкриття, перехоплення, передбачення, перебір і фіксація.

    • Приймання ідентифікатора. Пермісивний механізм заводить сесію під будь-яке значення від користувача, суворий — лише під те, що згенерував сам. Вимога: ніколи не приймати ідентифікатор, якого не видавав, а отримавши такий — видати новий. Перевірка — підставити в сесійну кукі вигадане значення.
    • Регенерація. Ідентифікатор must be renewed or regenerated після будь-якої зміни рівня привілеїв, і причина названа поіменно — запобігання фіксації. MDN каже те саме з боку кукі: перегенеровувати й надсилати сесійні кукі наново щоразу при автентифікації, навіть якщо вони вже існують. Інваріант тесту: значення до входу і після входу мусить відрізнятися.
    • Інвалідація. Гасити треба з обох боків, серверний названо найважливішим і обовʼязковим. Безпечне завершення складається з трьох частин: ручний вихід в інтерфейсі, завершення за бездіяльністю і належна інвалідація серверного стану — з окремим врахуванням випадку «користувач просто закрив вкладку».

    Четвертий момент — , і їх три.

    ТаймаутВідлікЩо дає
    idleвід останнього запиту сесіїзакриває забуту відкриту сесію
    absoluteвід створення, попри активністьобмежує час користування захопленою сесією
    renewalперіодично всередині живої сесіїскорочує вікно чинності значення

    Absolute — не дубль idle: захоплену сесію idle не спиняє, бо періодично генерує активність. Орієнтири джерела — 2–5 хвилин для високоцінних застосунків, 15–30 для низькоризикових, 4–8 годин для absolute; додає, що в банкінгу тримати неактивну сесію довше за 15 хвилин сенсу немає, а правильне значення взагалі є балансом безпеки й зручності. Головне: таймаут must be enforced server-side — «фронт розлогінює через 15 хвилин» перевіркою не є.

    І окремо від чотирьох моментів — паралельні сесії, які є рішенням дизайну, а не дефектом. Чи дозволені кілька одночасних входів, вирішує застосунок; але рішення «не можна» зобовʼязує діяти: після нової автентифікації неявно завершити попередню сесію або спитати користувача. Список активних сесій із віддаленим завершенням — теж не зручність, а названий захід захисту на випадок загубленого пристрою чи підозри на крадіжку кукі.

    Фіксація проти захоплення: дві атаки на той самий рядок

    Джерело наполягає на розведенні: (session fixation) не є класом (session hijacking) — хоч і веде до того самого результату, тому й стоїть у переліку пʼяти шляхів вище. Захоплення краде встановлену сесію після входу; фіксація закріплює сесію в браузері жертви, тож починається до входу. Корінь фіксації — у застосунку: під час автентифікації він не призначає новий ідентифікатор.

    СерверБраузер жертвиАтакувальникСерверБраузер жертвиАтакувальник1. Пересаджує свій ідентифікатор сесії2. Жертва входить під цим ідентифікаторомСесія автентифікована, ідентифікатор той самий3. Запит із тим самим ідентифікаторомДані автентифікованого користувачаСерверБраузер жертвиАтакувальникСерверБраузер жертвиАтакувальник1. Пересаджує свій ідентифікатор сесії2. Жертва входить під цим ідентифікаторомСесія автентифікована, ідентифікатор той самий3. Запит із тим самим ідентифікаторомДані автентифікованого користувача

    Підсаджують трьома способами: ідентифікатором у посиланні, формою логіну від атакувальника і заголовком Set-Cookie. Механізм описаний у RFC: кукі не дають гарантій цілісності для сусідніх доменів, тож активний мережевий атакувальник впроваджує кукі, видавши себе за відповідь по HTTP, — HTTPS-сервер не відрізнить її від власної. MDN додає: кукі з Domain, поставлена з піддомену, доступна на всіх інших піддоменах. Основний контрзахід — регенерація (mandatory саме проти фіксації); додатковий — префікси імені (__Secure- вимагає атрибута Secure і захищеного , __Host- ще й Path=/ та відсутності Domain), але браузери без підтримки префіксів приймають такі кукі завжди, тож це , не заміна регенерації.

    Захоплення лікується інакше. Крадіжка сесійної кукі має той самий вплив, що й крадіжка облікових даних, доки кукі не спливе; хоч би яким надійним був процес входу, проти неї він недостатній — MFA й стоять перед сесією. Причина: кукі приймають, не перевіряючи, хто їх надіслав, сервер перевіряє лише валідність значення. Крадуть їх зазвичай не із застосунку, а в користувача — шкідливим ПЗ чи фішингом, — тож єдиний хід сервісу — якнайшвидше виявити використання краденої кукі, зберігши інформацію про оточення при створенні сесії й порівнюючи її на кожному запиті. Обидві помилки методу задокументовані: зміна IP-Geo не доводить, а атака з тієї самої країни зміни не дасть узагалі. Найнадійніша реакція — повторна автентифікація з новою кукі, і ціна названа: надто часта повторна автентифікація псує досвід.

    Вихідна точка MDN: за замовчуванням усі значення кукі видимі кінцевому користувачеві й можуть бути ним змінені. Далі — рівно те, чого прапорці не роблять.

    • Secure закриває канал, і тільки канал. Він захищає лише конфіденційність: активний мережевий атакувальник може перезаписати Secure-кукі з незахищеного каналу, ламаючи цілісність, — підсадити кукі він не заважає. Обовʼязковим на сесійній куці однаково є: без нього атакувальник перенаправляє вихідний запит на HTTP, і браузер додасть кукі, навіть якщо сервер по HTTP не слухає.
    • HttpOnly закриває читання, і тільки читання. Він не блокує XSS і не спиняє виконання скриптів; спроба прочитати таку кукі зі скрипта повертає порожній рядок. Але кукі з HttpOnly все одно надсилається із запитами, ініційованими JavaScript, зокрема через fetch(), тож упроваджений скрипт діє від імені користувача, не бачачи значення. Прапорці незалежні — на сесійній куці мають стояти обидва.
    • Path безпекою не є: він не захищає від читання з іншого шляху й цілісності не дає, бо браузер приймає довільний Path у Set-Cookie.

    Три пастки, через які перевірка «прапорець є» нічого не доводить. Перша: рішення про надсилання ухвалює браузер, сервер лише оголошує атрибут — тож із завжди виставленим Secure сесії не працюють у середовищах по http, і це часта причина «на тесті логін не тримається», а не дефект застосунку. Друга: перевіряти треба відповідь, а не конфіг — при офлоуді TLS на балансер конфігураційне рішення не спрацює, а HttpOnly може, навпаки, дописати WAF, якщо правки коду неможливі. Третя, найпідступніша: браузер ігнорує нерозпізнані атрибути кукі, але не саму кукі — одруківка в назві прапорця (Secur, HttpOnl) нічого не ламає, вона мовчки лишає кукі без захисту, і видно це лише в заголовку Set-Cookie.

    :2025 зводить вимоги в одне речення: серверний вбудований менеджер сесій, який видає новий ідентифікатор після логіну; ідентифікатор не в URL, у захищеній кукі, інвалідується після виходу, простою та абсолютного таймауту. Ознаки вразливості названі там же — ідентифікатор у URL чи прихованому полі й повторне його використання після логіну. Навіщо Secure, ілюструє категорія : атакувальник знижує зʼєднання з HTTPS до HTTP і краде сесійну кукі.

    Зі сторінки прапорці не перевірити за побудовою: браузери блокують фронтенд-JavaScript від заголовка Set-Cookie (Fetch-специфікація робить його забороненим заголовком відповіді), а значення кукі не змінити власним заголовком Cookie через fetch(). ставлять на рівні або мережі; у Selenium робота з кукі — теж вбудована частина WebDriver-API, і набір доступних тесту полів задає специфікація, не сервер.

    const [sid] = (await context.cookies()).filter((c) => c.name === 'sid');
    expect(sid.httpOnly).toBe(true);
    expect(sid.secure).toBe(true);
    await context.addCookies([sid]); // цим же викликом повертають збережене значення після логауту

    SameSite: наскільки міцний це рубіж

    Дім теми — глава «CSRF і SameSite» цього розділу; тут лише те, що потрібно сесії. Атрибут обмежує область кукі так, що вона додається до запиту, лише якщо запит є same-site: Strict — тільки same-site, Lax — плюс крос-сайтові верхньорівневі навігації , None — усюди, і без Secure браузер таку кукі відкидає цілком.

    Два факти, через які тест дає несподіваний результат. Перший: кукі без атрибута і кукі з явним Lax — різні сценарії; типова поведінка Chrome дозвільніша, бо пропускає частину кукі на верхньорівневих POST, і чернетка дозволяє цей режим лише для кукі без явного SameSite, обмежує його віком кукі (розумною межею названо дві хвилини) і прямо каже, що це тимчасовий перехідний захід. Другий: межа атрибута — сайт, а не походження. Same-site означає однакову схему й однаковий (registrable domain, у щоденній мові eTLD+1), тож кукі з app.example.com лишається same-site для всього в .example.com — і уразливий піддомен знімає захист. Схему додано в означення навмисно, щоб HTTP не був слабким каналом.

    Сила заяви в джерел різна, і звести їх в одне речення не можна. Чернетка rfc6265bis: у суворому режимі й за підтримки клієнтом SameSite дає надійний захист від CSRF — із застереженням не робити цю позначку межею всього захисту сайту; про Lax та сама чернетка каже слабше — розумна глибина захисту проти атак на небезпечних методах, але не надійний захист від CSRF як категорії. OWASP: корисний як глибина захисту, але в більшості розгортань не замінює повноцінного CSRF-захисту. MDN: не повний захист, краще як доповнення до одного з інших. web.dev: ані Strict, ані Lax не є повним розвʼязанням безпеки сайту. Переказу «SameSite не захищає» не робить жодне з чотирьох.

    Найчастіша причина провалу на практиці — стано-змінна операція, доступна через GET: Lax блокує лише небезпечні методи. І межа інструмента: читання атрибута з тесту дока Selenium обіцяє в Chrome 80+, Firefox 79+ і Selenium 4 — на старішому стенді «перевірка пройшла» означає «перевірки не було».

    Тестування виходу: коли logout лише виглядає виходом

    Що коротше живуть токени сесії, то менша ймовірність успішного захоплення, — тому тестують окремо. Дефект, заради якого тест існує, варто знати напамʼять: клієнтському токену ставлять нове значення, а серверний стан лишається активним і його можна перевикористати, повернувши сесійну кукі до попереднього значення. В інтерфейсі вихід при цьому виглядає бездоганно; гірший варіант WSTG називає окремо — користувачеві показують лише повідомлення, не роблячи нічого.

    Процедура з двох кроків: зберегти значення кукі, якими впізнається сесія, і викликати вихід; якщо вихід підставив нові значення — повернути старі й перезавантажити сторінку із захищеної зони (технічно це context.addCookies() із прикладу вище). Очікуваний результат заданий джерелом: жодні дані, які має бачити лише автентифікований користувач, не мають бути видимі, а в ідеалі застосунок редіректить на публічну зону або форму входу. Що має статися з кукі, теж перевірне: сервер видаляє її явним заголовком — нова кукі з Expires у минулому або Max-Age, рівним нулю чи відʼємним, причому з тими самими імʼям, шляхом і доменом, інакше стара лишиться поруч; при конфлікті Max-Age має пріоритет. Але зникнення кукі доказом не є — саме тому крок із поверненням старого значення й існує.

    Три перевірки добирають решту:

    • Вихід за бездіяльністю. Люди закривають вкладку, не виходячи, тож сервер має завершити сесію сам; таймаут вимірюють серією запитів у захищену зону зі зростаючими паузами — коли зʼявиться поведінка виходу, приблизно дорівнює таймауту.
    • SSO. Єдиний вхід часто породжує кілька сесій, які завершуються окремо; очікування сформульоване прямо — виклик виходу і в застосунку, і в самій системі SSO має спричинити глобальне завершення всіх сесій. Це два різні тести.
    • Друга сесія того самого користувача. Чи гасить її вихід — питання до оголошеного рішення дизайну; кнопка виходу при цьому має бути доступна з кожної сторінки.

    Якщо сесія тримається на токені, серверного запису, який можна знищити, немає: для JWT потрібне окреме рішення для інвалідації — зазвичай відкликаних токенів, — і тоді сесії вже не повністю stateless. Найдешевший контроль лишається той самий: короткий час життя токена. «Вихід» у токенній схемі часто означає «чекати exp», і це має бути свідомим рішенням продукту.

    Поглиблення: JWT — що захищає підпис і де він ламається

    Формат токена й сімейства алгоритмів — тема глави Автентифікація та авторизація; тут атакувальний кут. Підпис накриває і захищені заголовки, і claims, і блокує рівно одне: підробку токена або зміну claims у наявному — наприклад, зміну ролі з простого користувача на адміністратора. Звідси базова негативна перевірка: правимо роль у payload, токен має відхилятися.

    Пастка перша — слабкий секрет симетричної схеми. При MAC той самий спільний секрет ділять емітент і аудиторія: ним і генерують, і перевіряють. Наслідок названо прямо — для кожної пари «емітент, аудиторія» потрібен різний секрет, інакше одна аудиторія може підробити токен, видаючи себе за емітента; у HS* перевіряльник — теж потенційний підробник. Асиметрична схема ваду знімає, бо публічний ключ може бути публічним. Вимога до секрету вимірювана: щонайменше такого ж розміру, як вихід алгоритму (256, 384 і 512 біт для HS256, HS384, HS512), і окремо — пароль як MAC-секрет не використовують. Читабельний рядок у конфізі — готова знахідка.

    Пастка друга — alg: none. (Unsecured JWT) — передбачений специфікацією вид токена: alg зі значенням none і порожній рядок на місці підпису. Модальності різні й усі важливі: створити такий токен реалізація MAY; приймати без явного дозволу застосунку — MUST NOT; приймати за замовчуванням — MUST NOT; а щоб помʼякшити downgrade-атаки, згоду заборонено оголошувати глобально — її дають на окремий обʼєкт. Плюс окремий MUST одержувачу: перевірити, що значення підпису — порожня послідовність октетів, тож alg: none із непорожньою третьою частиною — окремий негативний випадок.

    Чому пастка існує: підтримка none вимагається — сумісні реалізації зобовʼязані реалізувати лише HS256 і none. А алгоритм перевірки береться із самого токена: alg мусить бути присутній, зрозумілий реалізації й точно представляти вжитий алгоритм. Звузити перелік дозволених значень може тільки застосунок — це й шукають у коді, і це ж важіль проти ширшого класу, (algorithm substitution attack), у якій наявне значення підпису переграють під інший алгоритм.

    Речення, яке варто цитувати на співбесіді: навіть якщо JWT успішно валідується, застосунок SHOULD відхилити його, коли вжиті алгоритми для нього неприйнятні. Валідність не дорівнює прийнятності; провал будь-якого кроку валідації означає, що токен MUST бути відхилений як невалідний ввід. OWASP зводить це до вимоги: парсер не має приймати alg: none, у недавніх реалізаціях це вимкнено за замовчуванням. Процедура перевірки механічна: узяти чинний токен, підмінити alg на none, спорожнити третю частину (самої підміни недосить), очікувати ; окремими кейсами — те саме з непорожнім підписом і токен, підписаний неочікуваним алгоритмом. Передають токен схемою Bearer у заголовку Authorization, і лише захищеним транспортом.

    Поглиблення: час життя і refresh-токени

    Refresh-токен (refresh token) — обліковка, потрібна лише щоб отримати новий токен доступу, коли поточний став невалідним або протермінувався. Дві деталі випадають із переказів найчастіше: видача не обовʼязкова (це рішення сервера авторизації на підставі оцінки ), а адресат один — refresh-токени призначені тільки для серверів авторизації й ніколи не надсилаються на сервери ресурсів, тож поява такого токена в запиті до API є знахідкою без додаткового аналізу.

    Чому access-токен короткоживучий, сказано числом: сервери токенів SHOULD видавати короткоживучі bearer-токени — годину або менше, — особливо коли клієнт працює в браузері; строк повертає поле expires_in у секундах, і приклад специфікації 3600 означає годину. Самі refresh-токени MUST лишатися конфіденційними в передачі й у зберіганні та бути відомі лише серверу авторизації й тому клієнту, якому видані, а сервер MUST тримати привʼязку токена до клієнта. Для публічних клієнтів BCP додає: refresh-токен MUST бути або sender-constrained, або з ротацією.

    Ротація — про виявлення, а не про зміну значення. Із кожною відповіддю оновлення сервер видає новий refresh-токен, попередній анулює, але звʼязок зберігає. Сенс у тому, що коли токен скомпрометовано і ним користуються обидва — атакувальник і легітимний клієнт, — хтось предʼявить анульований, і це повідомить сервер авторизації про злам. Тому «старий refresh-токен просто не працює» — недостатній результат тесту: питання в тому, що робить сервер, побачивши анульований токен.

    Права токена доступу обмежують навмисно: він SHOULD бути обмежений аудиторією конкретного сервера ресурсів або невеликого їх набору. названі поіменно: token redirect — токен для одного сервера ресурсів приймає інший, помилково вважаючи його своїм; token replay — повторне використання вже вжитого токена. Коди відмови дають тесту : invalid_token (протермінований, відкликаний, зіпсований) — 401; insufficient_scope (токен чинний, прав бракує) — 403. І два місця, де токен не має зʼявлятися: bearer-токени SHOULD NOT передавати в URL сторінок, бо адреса з токеном із високою ймовірністю потрапить у логи, і їх MUST NOT зберігати в кукі, які можуть піти відкритим текстом, а хто зберігає токени в кукі — MUST ужити заходів проти CSRF. Відкликання в токенній схемі коштує статлесності: deny-list її забирає, а ключ deny-list не будують ні з сирого JWT, ні з його хешу — це дає обхід через malleability токена.

    Поглиблення: OAuth 2.0, PKCE і OpenID Connect

    Ролі й потік — тема тієї самої глави Автентифікація та авторизація. Безпековий кут тримається на трьох речах: адресі повернення, одноразовості коду й доказі володіння ним.

    redirect_uri. Адреса MUST бути абсолютним URI й не містити фрагмента; для публічних клієнтів реєстрація обовʼязкова — без вимоги реєстрації атакувальник може вжити endpoint авторизації як , — і сервер SHOULD вимагати повну адресу. Значення MUST зіставлятися принаймні з однією зареєстрованою адресою, а при розбіжності сервер SHOULD повідомити власника ресурсу і MUST NOT автоматично редіректити на невалідну адресу. Про другий крок згадують рідше: на обміні коду redirect_uri має бути ідентичне тому, що було в запиті авторизації. BCP посилює правило до точного порівняння рядків (виняток — номери портів у localhost-редиректах нативних застосунків) і пояснює емпірично: зіставлення за шаблоном виявилося складнішим і помилковішим, а успішні атаки на нього спостерігались на реальних сервісах. Реєстрація шаблоном «будь-який піддомен» — знахідка, а не питання зручності. Код авторизації одноразовий і короткоживучий: повторний обмін має провалитися незалежно від .

    PKCE (Proof Key for Code Exchange) закриває перехоплення коду в публічних клієнтів — тих, хто нездатний зберегти конфіденційність своїх облікових даних (SPA, нативні застосунки). Суть OWASP формулює одним реченням: крадений код без code_verifier на token endpoint обміняти не вдасться.

    Сервер авторизаціїБраузерКлієнт SPAСервер авторизаціїБраузерКлієнт SPAЗапит авторизації з code_challenge S256Перехід на endpoint авторизаціїКод авторизаціїПовернення на redirect_uri з кодомОбмін коду разом з code_verifierРахує challenge і звіряє з привʼязанимТокени або invalid_grantСервер авторизаціїБраузерКлієнт SPAСервер авторизаціїБраузерКлієнт SPAЗапит авторизації з code_challenge S256Перехід на endpoint авторизаціїКод авторизаціїПовернення на redirect_uri з кодомОбмін коду разом з code_verifierРахує challenge і звіряє з привʼязанимТокени або invalid_grant

    code_verifier — випадковий рядок від 43 до 128 символів; code_challenge рахують перетворенням plain або S256, і розбіжність на обміні дає MUST-помилку invalid_grant. Пастка дефолту готова до негативного тесту: code_challenge_method необовʼязковий і за відсутності дефолтиться в plain — пропущений параметр мовчки знижує захист. Клієнт, здатний на S256, MUST його вживати; plain SHOULD NOT застосовувати в новому коді; відкочуватися на plain після спроби S256 — прямий MUST NOT. З боку сервера захист від пониження описує OWASP: запит на токен із code_verifier приймається, лише якщо в запиті авторизації був code_challenge. Публічні клієнти MUST використовувати PKCE, конфіденційним він RECOMMENDED, а OWASP радить authorization code grant із PKCE для всіх типів клієнтів. Два гранти більше не застосовують: implicit (клієнти SHOULD NOT його вживати — токен тече через фрагмент URL, отже, через історію браузера, referrer і логи) і resource owner password credentials (MUST NOT — небезпечно віддає облікові дані клієнту). А CSRF на редирект- закриває будь-який із трьох механізмів: клієнти, які впевнилися в підтримці PKCE, MAY покластися на його захист; у потоках OpenID Connect той самий захист дає nonce; інакше обовʼязковим стає одноразовий state, привʼязаний до user agent. Тому «немає state — отже, дірка» автоматичним вердиктом не є.

    OpenID Connect (OIDC) — простий шар ідентичності над OAuth 2.0: дає клієнту перевірити особу кінцевого користувача за автентифікацією, яку виконав сервер авторизації. Нових вузлів не зʼявляється — це ті самі сервер авторизації (OpenID Provider) і клієнт (Relying Party) з додатковим обовʼязком. Головне розширення, яке OIDC додає до OAuth 2.0, — ID Token, і це той самий JWT, тож усі пастки підпису застосовні без змін. Клейм aud MUST містити client_id тієї самої Relying Party, а після exp токен MUST NOT прийматися. Валідація описана чотирма вимогами: iss мусить збігтися точно; токен MUST бути відхилений, якщо клієнта немає серед аудиторій або серед них є недовірені; підпис MUST валідуватися ключами емітента; а якщо в запиті надсилали nonce, однойменний клейм MUST бути присутній і звірений, і його SHOULD перевіряти на replay. Звідси набір перевірок: чужий ID Token, зайва аудиторія, підмінений iss, протермінований токен, повторений nonce; окремо транспорт — звернення до UserInfo MUST іти через TLS. Чужого провайдера тестувати не треба: предметом є те, як ваш застосунок обробляє повернутий код і токен, тож провайдера на рівні мережі.

    Типові помилки

    «Кукі після логауту зникла — вихід працює». Виглядає як успішний вихід, а насправді клієнтське значення могло змінитися при живому серверному стані. Доказ дає лише повернення старої кукі й запит у захищену зону.

    «Стоїть HttpOnly, значить XSS не страшний». Виглядає як захист від крадіжки, а насправді не блокує XSS: скрипт не прочитає значення, але кукі поїде з fetch()-запитом, і атака діє від імені користувача.

    «У конфізі прапорець є». Виглядає як доказ, а насправді доводить лише Set-Cookie у відповіді: TLS може офлоудитись на балансер, WAF — навпаки дописати прапорець, а одруківка в назві атрибута кукі не ламає, бо браузер ігнорує нерозпізнаний атрибут.

    «У потоці OAuth немає state — це дірка». Виглядає як знахідка, а насправді CSRF на редирект-ендпоінті закриває будь-який із трьох: PKCE, nonce або state.

    «Токен пройшов валідацію — доступ коректний». Виглядає як зелений шлях, а насправді валідність не дорівнює прийнятності: токен, підписаний алгоритмом, якого застосунок не очікує, має бути відхилений, навіть якщо підпис сходиться.

    Підсумок

    1. Ідентифікатор встановленої сесії тимчасово еквівалентний найсильнішому методу автентифікації. Усе, що після логіну, коштує стільки ж, скільки пароль.
    2. Фіксацію ловить один інваріант: значення сесійного ідентифікатора до входу і після має відрізнятися — і на будь-якій зміні привілеїв, не лише на логіні.
    3. Вихід і таймаут перевіряються повз інтерфейс. Повернуте старе значення кукі — єдиний доказ серверної інвалідації; таймаут рахує сервер.
    4. Кожен прапорець закриває рівно один канал: Secure — конфіденційність каналу, HttpOnly — читання зі скрипта, SameSite — частину крос-сайтових запитів. Проти фіксації не працює жоден із них — її закриває регенерація ідентифікатора; а SameSite жодне з чотирьох канонічних джерел не називає ані повним захистом, ані пустопорожнім.
    5. У токенній схемі відкликання коштує статлесності. Тому найдешевшим контролем лишається короткий час життя токена — і саме він визначає, що насправді означає «вийти».

    Можливі питання

    «Чим фіксація сесії відрізняється від захоплення?» Дивляться, чи розумієте ви момент атаки: у фіксації ідентифікатор підсаджують до входу й він відомий атакувальнику з початку, у захопленні його крадуть після. Перше лікується регенерацією, друге — виявленням і скороченням часу життя.

    «Як ви перевіряєте logout?» Питання-фільтр. Очікують не «натиснув і побачив форму входу», а процедуру: зняти значення сесійної кукі, вийти, повернути старе значення й звернутися в захищену зону повз інтерфейс. Плюс окремо SSO і друга сесія.

    «Що робить HttpOnly і чого він не робить?» Перевіряють, чи не вивчили ви прапорець гаслом. Сильна відповідь називає межу: він закриває читання зі скрипта (спроба дає порожній рядок), але не спиняє XSS і не заважає кукі поїхати з fetch()-запитом.

    «Що таке alg: none і чому це працює?» Слухають, чи знаєте ви, що це передбачений специфікацією вид токена з порожнім підписом, що реалізації зобовʼязані підтримувати none, і що алгоритм перевірки береться із самого токена — тому перелік дозволених значень мусить звузити застосунок.

    «Навіщо PKCE, якщо є state Дивляться, чи не плутаєте ви дві задачі: state — про CSRF на редирект-ендпоінті, PKCE — про перехоплений код авторизації. І дрібниця, що виказує практику: пропущений code_challenge_method мовчки дефолтиться в plain.

    «Чому в JWT складно зробити logout?» Хочуть почути розмін: підписаний токен перевіряють без звернення до сховища, тож дострокове відкликання потребує deny-list, а deny-list забирає ту саму статлесність, заради якої токен і брали. Звідси короткий exp як компроміс.

    Джерела

    Життєвий цикл сесії: чотири моменти, у яких вона ламається

    Фіксація проти захоплення: дві атаки на той самий рядок

    Прапорці cookie: що закриває кожен і чого він не закриває

    SameSite: наскільки міцний це рубіж

    Тестування виходу: коли logout лише виглядає виходом

    Поглиблення: JWT — що захищає підпис і де він ламається

    Поглиблення: час життя і refresh-токени

    Поглиблення: OAuth 2.0, PKCE і OpenID Connect

    • RFC 6749 — The OAuth 2.0 Authorization Framework — вимоги до redirect_uri та їх звірка, поведінка при розбіжності, , публічний клієнт і відкритий редиректор.
    • RFC 7636 — Proof Key for Code Exchange (PKCE) — перехоплення коду, довжина code_verifier, plain проти S256, дефолт plain, заборона відкату, invalid_grant.
    • RFC 9700 — Best Current Practice for OAuth 2.0 Security — точне порівняння рядків та його підстава, PKCE для публічних клієнтів, заборона implicit і password credentials, три механізми CSRF-захисту.
    • OWASP Cheat Sheet — OAuth2 — суть PKCE, захист від пониження, code grant для всіх клієнтів, витік токена через фрагмент URL.
    • OpenID Connect Core 1.0 — шар ідентичності, ролі OP і RP, ID Token як JWT, aud і exp, чотири вимоги валідації, TLS для UserInfo.
    • Playwright — Mock APIs — мокання зовнішнього провайдера на рівні мережі.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.