Middle: питання на співбесіді з теми «Автентифікація й секрети»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
Сесія: сервер зберігає стан (в базі, Redis, файлах), а клієнт має лише випадковий ID у cookie. Кожен запит - пошук сесії за ID.
- Миттєве відкликання: видалили сесію - доступу немає.
- Потрібне спільне сховище для кількох серверів.
JWT: токен сам містить дані (ID користувача, ролі, термін дії) і підписаний сервером. Перевірка - лише підпис, без звернення до сховища.
- Зручно між сервісами й доменами: будь-який сервіс з ключем перевірки довіряє токену.
- Відкликати до закінчення терміну складно: токен дійсний, поки не сплив. Звідси короткий термін access-токена (хвилини) плюс refresh-токен, або список відкликаних - що повертає стан на сервер.
- Дані в JWT не зашифровані, лише підписані: base64 розкодує будь-хто.
Де зберігати токен у браузері:
localStorage- доступний будь-якому JavaScript на сторінці. Одна XSS-вразливість - і токен викрадено й використано з іншого місця.- Cookie з
HttpOnly,Secure,SameSite- JavaScript токен не бачить. XSS усе ще може робити запити від імені користувача, але не може винести токен. Потрібен захист від CSRF (SameSite+ токен).
Для власного SPA на тому самому домені найпростіше й надійніше - звичайна сесія в cookie (як Sanctum у SPA-режимі). JWT і bearer-токени доречні для мобільних застосунків, інтеграцій сервер-сервер, мікросервісів.
Пастки JWT (RFC 8725): приймати лише очікуваний алгоритм (атака з alg: none і підміною алгоритму), перевіряти exp, iss, aud, не класти в токен секретних даних.
Фіксація сесії (session fixation): нападник заздалегідь отримує ID сесії (просто відкривши сайт) і змушує жертву використовувати цей самий ID - через посилання з параметром, вразливість на піддомені, що встановлює cookie. Жертва входить в обліковий запис, сесія стає автентифікованою - а її ID нападник уже знає.
Захист - новий ID сесії при зміні рівня привілеїв: після входу, після підвищення прав (вхід в адмінку, підтвердження пароля), після виходу.
if (Auth::attempt($credentials)) {
$request->session()->regenerate(); // новий ID, дані сесії збережено
return redirect()->intended('/dashboard');
}
// вихід
Auth::logout();
$request->session()->invalidate(); // знищити сесію
$request->session()->regenerateToken(); // новий CSRF-токен
Стартові набори Laravel (Breeze, Jetstream, Fortify) роблять це автоматично; у власній логіці входу про це легко забути.
Інші правила керування сесіями:
- ID лише в cookie (
HttpOnly,Secure,SameSite), ніколи в URL: звідти він потрапляє в логи, історію браузера й заголовокReferer. - Не приймати ID сесії, які сервер не видавав (строгий режим,
session.use_strict_modeу PHP). - Тайм-аути: неактивності (наприклад, 30 хвилин для чутливих застосунків) і абсолютний.
- Вихід на всіх пристроях після зміни пароля (
Auth::logoutOtherDevices()у Laravel). - Повторне підтвердження пароля перед критичними діями - зміною email, видаленням облікового запису.
Скидання пароля - «чорний хід» в обліковий запис. Якщо він слабший за вхід, атакують саме його.
Вимоги до токена скидання:
- випадковий і довгий - криптографічно стійкий генератор (
random_bytes,Str::random()), не похідний від email, часу чи id; - одноразовий - після використання недійсний;
- короткий термін дії - хвилини чи година;
- у базі - хеш токена, а не сам токен: витік бази не дає активних посилань для скидання;
- прив'язаний до облікового запису - токен одного користувача не працює для іншого.
Процес:
- користувач вводить email - відповідь однакова незалежно від того, чи існує обліковий запис («Якщо обліковий запис існує, ми надіслали лист»);
- лист із посиланням, що містить токен; відправка - через чергу (щоб час відповіді не видавав існування облікового запису);
- перехід за посиланням - форма нового пароля; токен перевіряється порівнянням хешів у постійному часі;
- новий пароль - з тими самими правилами, що й при реєстрації (довжина, перевірка у базі витоків);
- після зміни: токен знищено, усі інші сесії й токени API користувача відкликано, лист-сповіщення «ваш пароль змінено».
Пастки:
- отруєння посилання через заголовок Host: якщо URL у листі будується з
Hostзапиту, а сервер приймає довільні хости, зловмисник ініціює скидання для жертви з підробленимHost: evil.example- жертва отримує справжній лист з посиланням на домен зловмисника. Захист - фіксованийAPP_URLдля посилань і перевірка дозволених хостів; - витік токена через
Referer: сторінка скидання завантажує сторонні ресурси (аналітику, шрифти) - токен з URL потрапляє вReferer.Referrer-Policy: no-referrerна цій сторінці; - відповідь на секретні питання замість пошти - слабкий механізм, відповіді легко знайти в соцмережах;
- автоматичний вхід після скидання без MFA - обхід другого фактора;
- необмежена кількість запитів - спам листами на чужу адресу.
У Laravel брокер паролів (Password::sendResetLink, Password::reset) зберігає хеш токена в password_reset_tokens, має термін дії й обмеження частоти (throttle у config/auth.php). Після скидання варто викликати Auth::logoutOtherDevices() чи прибрати сесії й токени Sanctum.
Passkey - облікові дані на основі криптографії з відкритим ключем (стандарт WebAuthn/FIDO2) замість пароля.
Як це працює:
- реєстрація: пристрій користувача (телефон, ноутбук, апаратний ключ) створює пару ключів для конкретного сайту. Приватний ключ лишається на пристрої (в захищеному сховищі), сайт отримує й зберігає лише публічний;
- вхід: сервер надсилає випадковий виклик (challenge), пристрій підписує його приватним ключем після локального підтвердження (відбиток, обличчя, PIN пристрою), сервер перевіряє підпис публічним ключем.
Чому це сильніше за пароль + код:
- стійкість до фішингу. Ключ прив'язаний до домену сайту (relying party ID). Браузер просто не дасть використати ключ для
laravelukraine.comна сторінціlaravelukraine-login.com. Фальшивий сайт нічого не отримає - на відміну від пароля й навіть TOTP-коду, які користувач введе куди завгодно; - нічого вкрасти з сервера: у базі лише публічні ключі - їх витік нікому не допомагає;
- немає повторного використання: кожен сайт має свою пару ключів;
- немає перебору й credential stuffing - немає пароля;
- два фактори в одному: «маю пристрій» + «біометрія/PIN пристрою».
Синхронізовані й прив'язані до пристрою:
- синхронізовані passkeys (iCloud Keychain, Google Password Manager, 1Password) - доступні на всіх пристроях користувача, не губляться разом з телефоном;
- прив'язані до пристрою (апаратні ключі YubiKey) - максимальна безпека, але потрібен резервний ключ.
Що врахувати при впровадженні:
- відновлення доступу: втратили всі пристрої - потрібен запасний шлях (резервний passkey, перевірена пошта з додатковими перевірками), і він не повинен бути слабшим за сам passkey;
- поступовий перехід: спершу passkey як додатковий спосіб входу чи другий фактор, пароль поки лишається;
- кілька passkeys на обліковий запис - з назвами пристроїв і можливістю видалити;
- бібліотеки: реалізовувати WebAuthn вручну не варто - перевірка підпису, лічильників, атестації має багато тонкощів. Для PHP/Laravel є готові пакети, на фронтенді - API браузера
navigator.credentials.
Підтримка: усі сучасні браузери й операційні системи; великі сервіси (Google, Apple, GitHub, Microsoft) уже пропонують вхід без пароля.
«Запам'ятати мене» - користувач лишається автентифікованим тижні й місяці, навіть після закриття браузера, коли звичайна сесія вже завершилася.
Як це роблять правильно:
- окремий довгоживучий токен у cookie - випадковий, криптографічно стійкий, а не email, id чи хеш пароля;
- у базі - хеш токена (чи токен, прив'язаний до користувача, як у Laravel -
remember_token), щоб витік бази не давав готових cookie; - cookie з
HttpOnly,Secure,SameSite- недоступна JavaScript і не передається по HTTP; - при використанні токен створює нову звичайну сесію;
- при виході токен знищується на сервері, а не лише стирається cookie в браузері.
Ризики довгих сесій і що з ними робити:
- викрадена cookie діє довго. Обмежити абсолютний термін (наприклад, 30 днів), а для чутливих дій вимагати повторного підтвердження паролем (зміна email, пароля, MFA, платіжних даних) - так працює
password.confirmmiddleware в Laravel; - вихід «з усіх пристроїв» має справді анулювати remember-токени. У Laravel зміна
remember_token(наприклад, при зміні пароля чиAuth::logoutOtherDevices()) робить старі cookie недійсними; - «ротація» токена при кожному використанні з виявленням повторного використання - так помічають, що cookie скопійовано;
- список активних сесій у налаштуваннях облікового запису з пристроєм, місцем і часом останньої активності - користувач бачить чужий вхід і може його завершити (драйвер сесій
databaseдає таку можливість).
Таймаути сесій (рекомендації OWASP):
- неактивність - сесія завершується після N хвилин без дій (для банківських застосунків - хвилини, для звичайних сайтів - години);
- абсолютний - навіть активна сесія закінчується після певного часу;
- для адмінок і фінансових операцій - коротші терміни, ніж для звичайних користувачів.
Регенерація ідентифікатора сесії після входу, зміни прав і виходу - захист від фіксації сесії ($request->session()->regenerate()).
Компроміс: «Запам'ятати мене» - зручність коштом безпеки. Для публічних і спільних комп'ютерів його не варто вмикати за замовчуванням; для застосунків з чутливими даними варто обмежити або вимагати MFA при відновленні сесії з remember-токена.