Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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, не класти в токен секретних даних.

Докладніше в документації: RFC 8725: найкращі практики JWT

Фіксація сесії (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, видаленням облікового запису.

Докладніше в документації: OWASP: керування сесіями

Скидання пароля - «чорний хід» в обліковий запис. Якщо він слабший за вхід, атакують саме його.

Вимоги до токена скидання:

  • випадковий і довгий - криптографічно стійкий генератор (random_bytes, Str::random()), не похідний від email, часу чи id;
  • одноразовий - після використання недійсний;
  • короткий термін дії - хвилини чи година;
  • у базі - хеш токена, а не сам токен: витік бази не дає активних посилань для скидання;
  • прив'язаний до облікового запису - токен одного користувача не працює для іншого.

Процес:

  1. користувач вводить email - відповідь однакова незалежно від того, чи існує обліковий запис («Якщо обліковий запис існує, ми надіслали лист»);
  2. лист із посиланням, що містить токен; відправка - через чергу (щоб час відповіді не видавав існування облікового запису);
  3. перехід за посиланням - форма нового пароля; токен перевіряється порівнянням хешів у постійному часі;
  4. новий пароль - з тими самими правилами, що й при реєстрації (довжина, перевірка у базі витоків);
  5. після зміни: токен знищено, усі інші сесії й токени 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.

Докладніше в документації: OWASP: забутий пароль

Passkey - облікові дані на основі криптографії з відкритим ключем (стандарт WebAuthn/FIDO2) замість пароля.

Як це працює:

  1. реєстрація: пристрій користувача (телефон, ноутбук, апаратний ключ) створює пару ключів для конкретного сайту. Приватний ключ лишається на пристрої (в захищеному сховищі), сайт отримує й зберігає лише публічний;
  2. вхід: сервер надсилає випадковий виклик (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) уже пропонують вхід без пароля.

Докладніше в документації: Passkeys.dev: що таке passkeys

«Запам'ятати мене» - користувач лишається автентифікованим тижні й місяці, навіть після закриття браузера, коли звичайна сесія вже завершилася.

Як це роблять правильно:

  • окремий довгоживучий токен у cookie - випадковий, криптографічно стійкий, а не email, id чи хеш пароля;
  • у базі - хеш токена (чи токен, прив'язаний до користувача, як у Laravel - remember_token), щоб витік бази не давав готових cookie;
  • cookie з HttpOnly, Secure, SameSite - недоступна JavaScript і не передається по HTTP;
  • при використанні токен створює нову звичайну сесію;
  • при виході токен знищується на сервері, а не лише стирається cookie в браузері.

Ризики довгих сесій і що з ними робити:

  • викрадена cookie діє довго. Обмежити абсолютний термін (наприклад, 30 днів), а для чутливих дій вимагати повторного підтвердження паролем (зміна email, пароля, MFA, платіжних даних) - так працює password.confirm middleware в Laravel;
  • вихід «з усіх пристроїв» має справді анулювати remember-токени. У Laravel зміна remember_token (наприклад, при зміні пароля чи Auth::logoutOtherDevices()) робить старі cookie недійсними;
  • «ротація» токена при кожному використанні з виявленням повторного використання - так помічають, що cookie скопійовано;
  • список активних сесій у налаштуваннях облікового запису з пристроєм, місцем і часом останньої активності - користувач бачить чужий вхід і може його завершити (драйвер сесій database дає таку можливість).

Таймаути сесій (рекомендації OWASP):

  • неактивність - сесія завершується після N хвилин без дій (для банківських застосунків - хвилини, для звичайних сайтів - години);
  • абсолютний - навіть активна сесія закінчується після певного часу;
  • для адмінок і фінансових операцій - коротші терміни, ніж для звичайних користувачів.

Регенерація ідентифікатора сесії після входу, зміни прав і виходу - захист від фіксації сесії ($request->session()->regenerate()).

Компроміс: «Запам'ятати мене» - зручність коштом безпеки. Для публічних і спільних комп'ютерів його не варто вмикати за замовчуванням; для застосунків з чутливими даними варто обмежити або вимагати MFA при відновленні сесії з remember-токена.

Докладніше в документації: OWASP: керування сесіями