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

Питання на співбесіді: Автентифікація

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

10 питань

Верифікація email підтверджує, що користувач справді володіє вказаною адресою. Laravel має це з коробки.

// 1. Модель реалізує контракт
class User extends Authenticatable implements MustVerifyEmail {}

// 2. Маршрути захищають middleware
Route::get('/dashboard', ...)->middleware(['auth', 'verified']);

Як працює: після реєстрації Laravel шле лист із підписаним URL (signed URL), перехід за яким ставить email_verified_at. Middleware verified не пускає непідтверджених користувачів. Подія Registered автоматично тригерить відправку листа.

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

Auth::attempt() перевіряє облікові дані й, якщо вони правильні, відкриває сесію.

public function login(LoginRequest $request): RedirectResponse
{
    if (! Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
        throw ValidationException::withMessages(['email' => __('auth.failed')]);
    }

    $request->session()->regenerate();

    return redirect()->intended('/dashboard');
}

Покроково:

  1. Провайдер гарда шукає користувача за всіма полями, крім пароля.
  2. Пароль порівнюється з хешем через Hash::check() - відкритий пароль ніде не зберігається й не порівнюється напряму.
  3. При успіху ID користувача записується в сесію, і наступні запити бачать його через auth()->user().

Що робиться після входу:

  • session()->regenerate() - новий ідентифікатор сесії, щоб підкинутий заздалегідь не дав зловмиснику увійти разом з жертвою (session fixation).
  • redirect()->intended() - повертає туди, куди користувач ішов до перенаправлення на логін.

Додаткові умови передають тим самим масивом: Auth::attempt([...$credentials, 'is_active' => true]) не пустить вимкнений акаунт. У стартових наборах Laravel 13 усе це вже зроблено пакетом Fortify.

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

Звичайна сесія закінчується через SESSION_LIFETIME хвилин бездіяльності. «Запам'ятати мене» дозволяє лишатися в системі довше - без повторного входу.

Auth::attempt($credentials, remember: true);

Як це влаштовано: Laravel генерує випадковий токен, зберігає його в колонці users.remember_token і ставить довгоживучу зашифровану cookie. Коли сесія вже закінчилась, а cookie є, користувача автентифікують за нею, і Auth::viaRemember() повертає true.

Ризики й що з ними роблять:

  • Вкрадена cookie = доступ на весь термін її дії. Тому cookie має HttpOnly і Secure (через HTTPS), а на чутливих діях пароль запитують ще раз - middleware password.confirm.
  • Спільний комп'ютер. Галочка на бібліотечному ПК лишить акаунт відкритим для наступного. Тому вона не має бути ввімкнена за замовчуванням.
  • Вихід скидає токен. Auth::logout() оновлює remember_token, і стара cookie перестає працювати - тож «вийти» справді виходить.

Якщо viaRemember() повертає true, розумно обмежити найнебезпечніші дії, доки людина не підтвердить пароль.

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

Це різні рівні автентифікації:

  • Breeze - стартовий набір UI: реєстрація/вхід/скидання пароля на Blade+Livewire або React/Vue. Для швидкого старту.
  • Fortify - headless-бекенд автентифікації (без UI): логіка реєстрації, 2FA, скидання пароля. Під ним працює Breeze/Jetstream.
  • Sanctum - легка автентифікація для SPA (через cookie) та простих API-токенів. Дефолт для більшості API.
  • Passport - повноцінний OAuth2-сервер: видача access/refresh токенів стороннім клієнтам. Обирають, коли потрібен саме OAuth2.

Правило: SPA чи мобільний застосунок → Sanctum; «увійти через наш сервіс» для третіх сторін → Passport.

Докладніше в документації: Sanctum

Sanctum дає два режими:

1. API-токени - модель випускає токен, який клієнт шле в заголовку Authorization: Bearer ...:

$token = $user->createToken('mobile', ['posts:read'])->plainTextToken;

Route::middleware('auth:sanctum')->get('/user', fn (Request $r) => $r->user());

2. SPA-автентифікація - для односторінкових застосунків на тому ж домені використовує звичайні сесійні cookie + CSRF (без зберігання токенів у JS, що безпечніше).

Токени підтримують abilities (scopes): $user->tokenCan('posts:read'). Трейт HasApiTokens додає tokens()-зв'язок і createToken().

Докладніше в документації: Sanctum: API-токени

Автентифікація в Laravel тримається на двох поняттях з config/auth.php.

  • Гард визначає, як користувача впізнають у запиті: session - за сесією й cookie, sanctum - ще й за токеном у заголовку.
  • Провайдер визначає, звідки користувача беруть: зазвичай Eloquent-модель User чи таблиця бази.
'guards' => [
    'web'   => ['driver' => 'session', 'provider' => 'users'],
    'admin' => ['driver' => 'session', 'provider' => 'admins'],
],
'providers' => [
    'users'  => ['driver' => 'eloquent', 'model' => App\Models\User::class],
    'admins' => ['driver' => 'eloquent', 'model' => App\Models\Admin::class],
],

Коли другий гард виправданий: коли це справді інші люди в іншій таблиці - адміністратори окремо від клієнтів, з окремим входом і сесією.

Route::middleware('auth:admin')->group(fn () => /* ... */);

Auth::guard('admin')->attempt($credentials);

Коли не потрібен: якщо різниця лише в правах. «Адмін - теж користувач, але з роллю» - це авторизація (ролі, політики), а не другий гард. Зайвий гард подвоює логіку входу, скидання пароля й верифікації пошти.

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

Обмежити кількість невдалих спроб - за ключем, що поєднує email та IP.

public function authenticate(): void
{
    $key = Str::transliterate(Str::lower($this->input('email'))) . '|' . $this->ip();

    if (RateLimiter::tooManyAttempts($key, 5)) {
        throw ValidationException::withMessages([
            'email' => __('auth.throttle', ['seconds' => RateLimiter::availableIn($key)]),
        ]);
    }

    if (! Auth::attempt($this->only('email', 'password'))) {
        RateLimiter::hit($key);
        throw ValidationException::withMessages(['email' => __('auth.failed')]);
    }

    RateLimiter::clear($key);
}

Чому саме такий ключ:

  • Лише IP - заблокує цілий офіс за NAT, а зловмисник із сотнею адрес обійде.
  • Лише email - будь-хто зможе заблокувати вхід чужому акаунту, просто вводячи неправильний пароль.
  • Email + IP гальмує перебір конкретного акаунта з конкретної адреси.

Що ще допомагає:

  • однакове повідомлення «Невірний email або пароль» - щоб не підказувати, які email зареєстровані;
  • не блокувати акаунт назавжди після N спроб - це інструмент для атаки на доступність;
  • двофакторна автентифікація, після якої перебір пароля сам по собі нічого не дає.

Стартові набори Laravel 13 вже обмежують спроби входу. Якщо застосунок за проксі (Cloudflare), перевірте довірені проксі - інакше всі запити матимуть IP проксі.

Докладніше в документації: Обмеження спроб входу

2FA вимагає двох факторів: «що знаю» (пароль) + «що маю» (код із застосунку/SMS). Найпоширеніше - TOTP (Time-based One-Time Password) сумісно з Google Authenticator.

У Laravel найпростіше через Fortify, який має 2FA з коробки:

  • генерація секрету й QR-коду для прив'язки;
  • перевірка 6-значного коду при вході;
  • одноразові recovery codes на випадок втрати пристрою.
// Fortify вмикає features:
Features::twoFactorAuthentication(['confirm' => true]),

Під капотом - пакет pragmarx/google2fa. Важливо: зберігати секрет зашифрованим, давати recovery-коди, за бажанням «запам'ятати пристрій».

Докладніше в документації: Двофакторна автентифікація (Fortify)

Типовий випадок - користувач змінив пароль, бо підозрює злам, і стара сесія на чужому пристрої має перестати працювати.

Для сесій у браузері:

Route::middleware(['auth', 'auth.session'])->group(function () {
    // ...
});

Auth::logoutOtherDevices($request->input('current_password'));

logoutOtherDevices() перехешовує пароль, а middleware auth.session (AuthenticateSession) на кожному запиті порівнює хеш, збережений у сесії, з поточним. На інших пристроях хеші більше не збігаються - сесія закривається. Без auth.session на маршрутах виклик нічого не дає, і це найчастіша помилка.

Для API-токенів Sanctum механізм інший - токени відкликають явно:

$user->tokens()->delete();                          // усі
$user->tokens()->where('id', $tokenId)->delete();   // один пристрій

«Запам'ятати мене» скидається через новий remember_token.

Повна картина для користувача - сторінка «Активні сесії» з пристроями й можливістю закрити окрему. Для цього сесії мають бути в базі (драйвер database), щоб їх можна було перелічити й видалити за user_id.

Після зміни пароля варто також надіслати лист «Ваш пароль змінено» - якщо це зробив не власник, він дізнається одразу.

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

Паролі зберігають лише як повільний односторонній хеш - bcrypt чи argon2id через Hash::make().

  • Не шифрування: зашифроване відновлюється ключем, і витік ключа відкриває всі паролі.
  • Не SHA-256 чи MD5: вони швидкі, і на відеокарті перебирають мільярди варіантів за секунду.
  • Сіль генерується для кожного пароля окремо й зберігається в самому рядку хешу - окремо нічого не треба.

Посилення без скидання. Параметри хешування з часом піднімають - наприклад, BCRYPT_ROUNDS з 12 до 13. Старі хеші при цьому лишаються робочими, бо параметри записані в них самих.

Оновити їх можна лише в момент, коли відомий відкритий пароль, - при вході:

if (Hash::needsRehash($user->password)) {
    $user->update(['password' => Hash::make($plainPassword)]);
}

У Laravel це робиться автоматично: опція rehash_on_login у config/hashing.php увімкнена за замовчуванням, і після успішного входу хеш перезаписується з новими параметрами.

Перехід на інший алгоритм (bcrypt → argon2id) потребує ще одного кроку. За замовчуванням Hash::check() відхиляє хеш, створений іншим алгоритмом, і кидає RuntimeException - це захист від підміни хешу. На час переходу ставлять HASH_VERIFY=false, і тоді перезапис при вході поступово переведе активних користувачів. Неактивні лишаться на старому, і для них можна запланувати примусове скидання через рік-два, а потім повернути перевірку.

Докладніше в документації: Визначення потреби в перехешуванні