Модуль 28

Захищені маршрути та авторизація

Ось готовий урок, створений за твоїм майстер-промптом. Вмикаємо режим CS50! 🚀


🛡️ Урок: Захищені маршрути та авторизація

Привіт, друзі! Мене звати [Твоє Ім'я], і це... CS50 (ну, майже)!

Сьогодні ми не просто пишемо код. Сьогодні ми стаємо охоронцями власної фортеці. Готові? Поїхали!


1. 🔥 Вступ: Чому не можна просто "увійти"?

Уявіть, що ви збудували шикарний готель. У вас є номери «Стандарт» і є «Президентський люкс» із джакузі та безкоштовним мінібаром.

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

Звісно, ні! Це було б катастрофою для бізнесу.

У веб-розробці це працює так само. У вашому додатку є сторінки, які доступні всім (Головна, «Про нас»), а є «Президентські люкси» — це Особистий кабінет, Налаштування, Адмін-панель.

Якщо ми не захистимо ці маршрути (routes), будь-хто, хто знає URL-адресу /admin/delete-all-users, зможе знищити ваш проєкт одним кліком.

Сьогодні ми навчимося ставити надійного швейцара перед дверима наших важливих сторінок. Цей швейцар називається Protected Route (Захищений маршрут).


2. 🧠 Теоретична база: Як працює "швейцар"

Давайте заглянемо під капот. Тут немає ніякої магії, лише проста логіка. Але спочатку — два терміни, які плутають 90% початківців.

🔑 Аутентифікація vs Авторизація

  1. Аутентифікація (Authentication): Відповідь на питання «Хто ти?».
    • Аналогія: Ви показуєте паспорт на кордоні. Прикордонник бачить: "Ага, це Іван".
  2. Авторизація (Authorization): Відповідь на питання «Що тобі можна робити?».
    • Аналогія: Паспорт у вас є, але чи є у вас віза або квиток у VIP-зону?

Як це працює в коді?

Уявіть маршрутизатор (Router) як коридор з дверима. Без захисту: Людина натискає на ручку дверей /dashboard -> Двері відчиняються -> Людина бачить контент.

Із захистом (Protected Route): Людина натискає на ручку /dashboard -> СТОП!

Тут вмикається наш код-перехоплювач (часто це компонент-обгортка або Middleware). Він робить перевірку:

ЯКЩО (користувач_залогінений == TRUE) {
    Пропускаємо далі (Render Component)
} ІНАКШЕ {
    Відправляємо на сторінку входу (Redirect to /login)
}

Обовʼязково запам’ятати: Захищений маршрут на клієнті (фронтенді) — це лише візуальна зручність. Справжня безпека даних завжди знаходиться на сервері (бекенді). Але сьогодні ми фокусуємося на тому, як не пустити "чужинця" на сторінку інтерфейсу.


3. 🧪 Приклади (від ідеї до коду)

Давайте напишемо це. Я буду використовувати синтаксис React, оскільки це найпопулярніший приклад, але логіка ідентична для Vue, Angular чи навіть звичайного HTML/JS.

Приклад 1: "Наївний" підхід (Мінімальний)

Уявіть, що у вас є компонент Dashboard.

Що ви очікуєте побачити в коді? Напевно, просто перевірку if всередині компонента.

function Dashboard({ user }) {
  // Якщо користувача немає — викидаємо його
  if (!user) {
    return <Navigate to="/login" />;
  }

  // Якщо є — показуємо секретні дані
  return <h1>Привіт, {user.name}! Ось твій баланс: $1,000,000</h1>;
}

Чому це погано? Уявіть, що у вас 50 таких сторінок. Вам доведеться писати цей if п'ятдесят разів! Це порушує принцип DRY (Don't Repeat Yourself).

Приклад 2: Паттерн "Охоронець" (Типовий для реальних проєктів)

Давайте створимо окремий компонент-обгортку. Назвемо його ProtectedRoute. Це і є наш швейцар.

// Компонент-швейцар
const ProtectedRoute = ({ user, children }) => {
  // 1. Перевірка: чи є користувач?
  if (!user) {
    // 2. Якщо ні — до побачення, йдіть логінитись
    return <Navigate to="/login" replace />;
  }

  // 3. Якщо так — проходьте (рендеримо дочірні компоненти)
  return children;
};

А тепер ми використовуємо його в нашому додатку:

<Routes>
  <Route path="/" element={<Home />} />

  {/* Загортаємо секретну сторінку в охоронця */}
  <Route
    path="/dashboard"
    element={
      <ProtectedRoute user={currentUser}>
        <Dashboard />
      </ProtectedRoute>
    }
  />
</Routes>

Бачите красу? Компонент Dashboard тепер нічого не знає про перевірки. Він просто робить свою роботу. А ProtectedRoute займається безпекою.

Приклад 3: "Тільки для адмінів" (Трохи складніше)

А що, якщо користувач залогінений, але він звичайний студент, а хоче зайти в "Учительську"?

const AdminRoute = ({ user, children }) => {
  // Спочатку перевіряємо, чи він взагалі увійшов
  if (!user) {
    return <Navigate to="/login" />;
  }

  // А тепер перевіряємо РОЛЬ (Авторизація)
  if (user.role !== 'admin') {
    return <h1>Вибачте, доступ заборонено! Тільки для босів. 🚫</h1>;
  }

  return children;
};

4. 🛠 Практична частина

Час забруднити руки кодом! Відкрийте свій редактор (або уявіть його) і спробуйте вирішити ці задачі.

🔹 Завдання 1: Повторення Напишіть функцію ProtectedRoute на папері або в редакторі, не підглядаючи в приклад вище. Логіка: "Немає юзера? Редірект. Є? Рендер children".

🔹 Завдання 2: "А куди я йшов?" (UX кейс) Коли користувача викидає на логін, це дратує, якщо після входу його кидає на "Головну", а не туди, куди він хотів. Задача: Дослідіть (або придумайте), як передати поточний шлях в сторінку /login, щоб після успішного входу повернути людину назад.

🔹 Завдання 3: Стан завантаження У реальному житті перевірка користувача займає час (запит на сервер). Задача: Додайте в ProtectedRoute умову: if (isLoading) return <Spinner />. Що буде, якщо цієї умови не буде? (Спойлер: користувача може викинути на логін ще до того, як сервер скаже "Привіт").

🔹 Завдання 4: Міні-кейс "Guest Route" Ми захистили /dashboard від незареєстрованих. Але тепер захистіть сторінку /login від... зареєстрованих! Задача: Якщо я вже увійшов у систему і йду на /login, мене має перекинути одразу в /dashboard. Навіщо мені логінитись двічі?


5. 💡 Мислення як у розробника

Ви вже знаєте синтаксис. Тепер давайте навчимося думати як сеньйор-розробник.

❌ Помилка новачка: "Ілюзія безпеки"

Новачок думає: "Я приховав кнопку 'Видалити' на фронтенді і поставив ProtectedRoute. Все, хакери не пройдуть!"

Як думає профі: Профі знає, що Frontend-код виконується в браузері користувача. Я можу відкрити консоль розробника, змінити змінну isAdmin = true і ваш ProtectedRoute пропустить мене. Тому: 1. Frontend захист — це для зручності (User Experience), щоб юзер не тицяв туди, куди не можна. 2. Backend захист — це для безпеки. Кожен API запит на сервер має перевіряти токен ще раз.

💡 Порада з практики

Не зберігайте статус авторизації просто як true/false в локальному стейті компонента. Використовуйте глобальний стейт (Context, Redux, Zustand). Чому? Бо коли ви оновите сторінку (F5), локальний стейт зникне, і користувача викине, навіть якщо він залогінений.


6. 🧩 Підсумок

Отже, що ми сьогодні зробили?

  1. Ми зрозуміли, що не всі двері мають бути відчинені.
  2. Ми створили компонент-швейцара (ProtectedRoute), який перевіряє перепустки.
  3. Ми розібрали різницю між "Хто ти?" (Аутентифікація) і "Чи можна тобі?" (Авторизація).

Тепер ви вмієте будувати безпечні маршрути у ваших додатках. Ваш додаток більше не прохідний двір!

🕵️ Тизер наступного уроку: Добре, швейцар працює. Але звідки він знає, що перепустка справжня? Наступного разу ми поговоримо про JWT токени, LocalStorage та Cookies. Ми дізнаємось, як зберігати секрети так, щоб їх не вкрали.

А поки що — це був CS50. Щасти вам!