Механізми автентифікації у вебзастосунках

Стратегії автентифікації

Стратегії автентифікації

Короткий зміст

У цій лекції розглядається огляд існуючих підходів до автентифікації у веб-застосунках:

  • Session-based (Stateful) — автентифікація на основі серверних сесій з використанням cookies, зберігання стану на сервері (Redis, БД)
  • Token-based (Stateless) — автентифікація за допомогою JWT токенів, відсутність серверного стану, передача токена у заголовку Authorization
  • OAuth 2.0 — делегована авторизація через сторонні провайдери (Google, GitHub, Facebook), потоки авторизації
  • Single Sign-On (SSO) — єдиний вхід для корпоративних застосунків через SAML або OpenID Connect

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


🎯 Мета лекції

  • Опанувати фундаментальні підходи до автентифікації у сучасних веб-застосунках.
  • Зрозуміти концептуальну різницю між stateful та stateless архітектурами автентифікації.
  • Навчитися обирати оптимальну стратегію залежно від вимог проєкту та архітектурних обмежень.
  • Ознайомитися з делегованою автентифікацією через OAuth 2.0 та концепцією Single Sign-On для корпоративних систем.

🔑 Ключові терміни

  • Stateful Authentication: підхід, де стан сесії користувача зберігається на сервері або у розподіленому сховищі.
  • Stateless Authentication: підхід, де всі дані про автентифікацію містяться у токені, переданому клієнтом.
  • Session ID: унікальний ідентифікатор серверної сесії, що зберігається у cookie браузера.
  • Bearer Token: токен доступу, що передається у заголовку Authorization: Bearer <token> кожного запиту.

Фундаментальна дихотомія: Stateful vs Stateless

Вибір стратегії автентифікації визначається фундаментальним архітектурним рішенням — де система зберігатиме стан сесії користувача (session state). Це рішення впливає на масштабованість, безпеку, складність інфраструктури та можливості горизонтального розширення системи.

Stateful автентифікація (автентифікація зі збереженням стану) означає, що після успішного входу сервер створює запис сесії у своїй пам'яті або у зовнішньому сховищі (Redis, PostgreSQL) і присвоює їй унікальний ідентифікатор — session ID. Цей ідентифікатор передається клієнту у вигляді cookie, і кожен наступний запит від браузера автоматично включає цей cookie. Сервер отримує session ID, шукає відповідну сесію у сховищі і витягує з неї дані користувача — ідентифікатор, ролі, час останньої активності.

Stateless автентифікація (автентифікація без збереження стану) означає, що сервер не зберігає жодної інформації про активні сесії користувачів. Замість цього після успішного входу сервер генерує токен (token) — підписану структуру даних, що містить ідентифікатор користувача, ролі, час видачі та час закінчення терміну дії. Цей токен передається клієнту, який зберігає його локально (у localStorage, sessionStorage або cookie) і відправляє у заголовку Authorization кожного запиту. Сервер перевіряє підпис токена, витягує дані користувача безпосередньо з токена і не звертається до бази даних чи Redis для пошуку сесії.

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

Діаграма архітектурних підходів

Розглянемо візуальне порівняння двох підходів у момент обробки запиту від автентифікованого користувача:

Loading diagram...
graph LR
    subgraph "Stateful (Session-Based)"
        A1[Client] -->|Cookie: sessionId=abc123| B1[Server]
        B1 -->|Пошук сесії| C1[(Redis/Database)]
        C1 -->|userId, roles| B1
        B1 -->|200 OK| A1
    end
    
    subgraph "Stateless (Token-Based)"
        A2[Client] -->|Authorization: Bearer JWT| B2[Server]
        B2 -->|Перевірка підпису| B2
        B2 -->|Декодування payload| B2
        B2 -->|200 OK| A2
    end
    
    style A1 fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style A2 fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B1 fill:#8b5cf6,stroke:#6d28d9,color:#ffffff
    style B2 fill:#8b5cf6,stroke:#6d28d9,color:#ffffff
    style C1 fill:#10b981,stroke:#047857,color:#ffffff

Ключова відмінність: у stateful підході сервер залежить від зовнішнього сховища для отримання даних користувача, тоді як у stateless підході сервер самодостатній — йому достатньо перевірити підпис токена за допомогою свого секретного ключа.


Session-Based автентифікація (Stateful)

Session-based автентифікація є історично першим та інтуїтивно зрозумілим підходом, що виник разом із динамічними веб-застосунками у середині 1990-х років. Цей підхід природно поєднується з механізмом HTTP cookies, вбудованим у всі сучасні браузери.

Життєвий цикл сесії

Розглянемо покрокову послідовність створення, використання та знищення сесії у типовому веб-застосунку:

Крок 1. Вхід користувача (Login)

Користувач надсилає POST-запит на ендпоінт /auth/login з обліковими даними — email та паролем. Сервер перевіряє пароль (порівнює bcrypt хеш), і якщо автентифікація успішна, генерує унікальний session ID за допомогою криптографічно стійкого генератора випадкових чисел.

// Спрощений приклад генерації session ID
import { randomBytes } from 'crypto';

function generateSessionId(): string {
  return randomBytes(32).toString('hex'); // 64 символи у hex форматі
}

const sessionId = generateSessionId();
// Приклад: "a3f5c8e2d9b1f4e7a6c3d8e9f2b5a7c4..."

Крок 2. Збереження сесії на сервері

Сервер створює запис сесії у сховищі (Redis, PostgreSQL, MongoDB) з асоційованими даними користувача:

// Структура даних сесії
interface Session {
  sessionId: string;
  userId: string;
  email: string;
  roles: string[];
  createdAt: Date;
  expiresAt: Date;
  lastActivityAt: Date;
  ipAddress: string;
  userAgent: string;
}

Для швидкого доступу часто використовують Redis з автоматичним TTL (time to live):

await redis.setex(
  `session:${sessionId}`,
  3600, // TTL 1 година
  JSON.stringify({
    userId: user.id,
    email: user.email,
    roles: user.roles,
  })
);

Сервер встановлює HTTP cookie з session ID у відповіді на успішний логін:

response.cookie('sessionId', sessionId, {
  httpOnly: true,    // Захист від XSS — cookie недоступне через JavaScript
  secure: true,      // Передача лише через HTTPS
  sameSite: 'lax',   // Захист від CSRF атак
  maxAge: 3600000,   // 1 година у мілісекундах
  path: '/',         // Cookie діє для всього сайту
});

Браузер автоматично включає cookie у заголовки всіх наступних запитів до того самого домену:

GET /api/v1/users/profile HTTP/1.1
Host: example.com
Cookie: sessionId=a3f5c8e2d9b1f4e7a6c3d8e9f2b5a7c4...

Сервер витягує session ID з cookie, шукає сесію у Redis, завантажує дані користувача і обробляє запит.

Крок 5. Оновлення часу останньої активності

Для реалізації механізму sliding expiration (ковзного закінчення терміну) сервер оновлює поле lastActivityAt при кожному запиті:

await redis.expire(`session:${sessionId}`, 3600); // Продовжити TTL на 1 годину

Це дозволяє автоматично закривати неактивні сесії, але зберігати активні навіть після закінчення початкового терміну.

Крок 6. Вихід користувача (Logout)

При явному виході користувач надсилає запит на /auth/logout. Сервер видаляє сесію зі сховища та скидає cookie:

await redis.del(`session:${sessionId}`);
response.clearCookie('sessionId');

Переваги session-based автентифікації

✅ Серверний контроль

Сервер має повний контроль над усіма активними сесіями. Адміністратор може в будь-який момент знищити конкретну сесію (примусовий вихід користувача) або всі сесії користувача одночасно (наприклад, при зміні паролю). Це критично важливо для безпеки корпоративних систем.

✅ Легке відкликання

При блокуванні користувача або зміні його ролей достатньо видалити або оновити записи сесій у Redis — зміни набувають чинності негайно. Немає необхідності чекати, поки застаріють токени.

✅ Менший розмір даних у запитах

Cookie містить лише короткий ідентифікатор (32–64 байти), тоді як JWT токен може займати 500–1500 байтів через закодовані claims та підпис. Це економить трафік, особливо у мобільних мережах.

✅ Природна інтеграція з браузерами

Механізм cookies підтримується усіма браузерами з автоматичною передачею у заголовках. Розробнику не потрібно вручну додавати заголовок Authorization до кожного fetch/axios запиту.

Недоліки та архітектурні обмеження

❌ Складність масштабування

При горизонтальному масштабуванні (кілька серверів застосунків) виникає проблема розподіленого стану. Якщо користувач увійшов на сервері A, а наступний запит потрапив на сервер B, сесія не буде знайдена. Вирішення вимагає централізованого сховища (Redis Cluster) або sticky sessions (закріплення клієнта за сервером) на рівні балансувальника навантаження.

❌ Навантаження на сховище

Кожен запит вимагає звернення до Redis або бази даних для завантаження даних сесії. При великій кількості активних користувачів (сотні тисяч одночасних сесій) це створює значне навантаження на сховище та збільшує затримки відповідей (latency).

❌ Обмеження у мобільних та SPA застосунках

Cookies природно працюють лише у браузерах. Мобільні застосунки (iOS, Android) не мають вбудованого механізму cookies і вимагають ручної реалізації зберігання session ID та його передачі у заголовках, що нівелює основну перевагу підходу.

❌ Проблеми з CORS та крос-доменними запитами

Якщо frontend розміщено на домені app.example.com, а API на api.example.com, браузер не передає cookies у крос-доменних запитах за замовчуванням. Потрібна точна конфігурація CORS з credentials: 'include' та атрибутом SameSite=None; Secure.
Sticky Sessions вважаються антипатерном у сучасній архітектурі мікросервісів. Вони прив'язують клієнта до конкретного екземпляра сервера, що ускладнює динамічне масштабування, graceful shutdown та рівномірний розподіл навантаження. Замість sticky sessions рекомендується використовувати централізоване сховище сесій (Redis Sentinel/Cluster) або перейти до stateless автентифікації.

Token-Based автентифікація (Stateless)

Token-based автентифікація набула популярності з розвитком Single Page Applications (SPA) та мобільних застосунків, де традиційний механізм cookies виявився обтяжливим. Найпоширенішим форматом токенів є JSON Web Token (JWT) — стандарт RFC 7519, що визначає компактний та самодостатній формат для безпечної передачі claims між сторонами.

Архітектура JWT токенів

JWT токен складається з трьох частин, розділених крапками:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMCIsImVtYWlsIjoiaXZhbkBleGFtcGxlLmNvbSIsInJvbGVzIjpbInVzZXIiXSwiaWF0IjoxNjkzNTY0ODAwLCJleHAiOjE2OTM1Njg0MDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Структура токена:

  1. Header (Заголовок): містить тип токена (JWT) та алгоритм підпису (HS256, RS256, ES256).
  2. Payload (Корисне навантаження): містить claims — дані про користувача, ролі, час видачі та закінчення.
  3. Signature (Підпис): криптографічний підпис перших двох частин, що гарантує цілісність та автентичність токена.

Кожна частина кодується у Base64URL (модифікація Base64 для безпечного використання у URL).

JWT токени не є зашифрованими — вони лише підписані. Будь-хто може декодувати Base64URL та прочитати вміст payload. Тому ніколи не зберігайте у JWT конфіденційні дані на кшталт паролів, номерів кредитних карток або приватних ключів. Токен призначений лише для передачі ідентифікатора користувача та метаданих для авторизації.

Життєвий цикл JWT автентифікації

Крок 1. Вхід користувача та генерація токена

Після успішної перевірки паролю сервер генерує JWT токен з даними користувача:

import { sign } from 'jsonwebtoken';

const accessToken = sign(
  {
    sub: user.id,              // Subject — ідентифікатор користувача
    email: user.email,
    roles: user.roles,
    iat: Math.floor(Date.now() / 1000),  // Issued At — час видачі
  },
  process.env.JWT_SECRET,      // Секретний ключ для підпису
  {
    expiresIn: '15m',          // Токен діє 15 хвилин
    algorithm: 'HS256',        // Алгоритм підпису HMAC SHA-256
  }
);

Крок 2. Передача токена клієнту

Сервер повертає токен у JSON-відповіді:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "tokenType": "Bearer",
  "expiresIn": 900
}

Крок 3. Зберігання токена на клієнті

Frontend зберігає токен локально — зазвичай у localStorage або sessionStorage:

// Збереження токена після логіну
localStorage.setItem('accessToken', response.accessToken);

// Витягування токена для наступних запитів
const token = localStorage.getItem('accessToken');
Зберігання токенів у localStorage робить їх вразливими до XSS атак (Cross-Site Scripting). Якщо зловмисник впровадить шкідливий JavaScript на сторінку (через вразливість у коді або сторонню бібліотеку), він зможе зчитати токен та надіслати його на свій сервер. Альтернативою є зберігання у HttpOnly cookies, але це повертає проблеми з CORS. Ми розглянемо безпечні підходи детальніше у лекції про refresh tokens.

Крок 4. Передача токена у заголовках запитів

При кожному запиті до API frontend додає токен у заголовок Authorization:

const response = await fetch('https://api.example.com/users/profile', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
});

Крок 5. Перевірка токена на сервері

Сервер витягує токен з заголовка, перевіряє підпис та декодує payload:

import { verify } from 'jsonwebtoken';

try {
  const payload = verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],
  });
  
  // Токен дійсний — дані користувача доступні у payload
  const userId = payload.sub;
  const roles = payload.roles;
  
} catch (error) {
  // Токен недійсний, застарів або підроблений
  throw new UnauthorizedException('Invalid token');
}

Критично важливо: сервер не звертається до бази даних для перевірки токена. Він лише перевіряє криптографічний підпис за допомогою свого секретного ключа. Якщо підпис співпадає, сервер довіряє вмісту токена.

Переваги token-based автентифікації

✅ Горизонтальне масштабування

Сервери застосунків є повністю stateless — вони не зберігають жодного стану між запитами. Це дозволяє запускати десятки або сотні екземплярів сервера без необхідності синхронізації стану. Балансувальник навантаження може розподіляти запити довільно, без sticky sessions.

✅ Відсутність навантаження на сховище

Сервер не виконує запитів до Redis або бази даних для перевірки токена. Це радикально знижує затримки відповідей (latency) та зменшує вимоги до інфраструктури. Один екземпляр сервера може обробляти тисячі запитів на секунду.

✅ Універсальність для різних платформ

JWT токени працюють однаково у веб-браузерах, мобільних застосунках (iOS, Android), desktop застосунках та між мікросервісами. Немає залежності від механізму cookies — токен передається у стандартному HTTP-заголовку.

✅ Крос-доменна сумісність

Токени передаються у заголовках, тому немає проблем з CORS та крос-доменними запитами. Frontend на app.example.com може вільно викликати API на api.example.com без складної конфігурації cookies.

Недоліки та ризики безпеки

❌ Неможливість миттєвого відкликання

Оскільки токен є самодостатнім і не вимагає серверної перевірки, сервер не може відкликати токен до закінчення його терміну дії. Якщо користувача заблоковано або змінено його ролі, видані раніше токени залишаються дійсними до exp claim. Вирішення вимагає додаткової інфраструктури — blacklist токенів у Redis або скорочення терміну життя токенів до 5–15 хвилин з механізмом refresh tokens.

❌ Більший розмір запитів

JWT токен займає 500–1500 байтів залежно від кількості claims. Цей токен передається у кожному запиті, на відміну від session ID (32–64 байти). При великій кількості запитів це збільшує трафік та навантаження на мережу.

❌ Вразливість до XSS атак

Якщо токен зберігається у localStorage або sessionStorage, він доступний для будь-якого JavaScript коду на сторінці. Успішна XSS атака дозволяє зловмиснику викрасти токен та видавати себе за користувача до закінчення терміну дії токена.

❌ Складність управління секретними ключами

Секретний ключ для підпису токенів (JWT_SECRET) має зберігатися у безпеці та регулярно ротуватися. При компрометації ключа всі видані токени стають вразливими. У розподілених системах доводиться синхронізувати ключі між усіма сервісами або переходити до асиметричного шифрування (RS256) з публічними/приватними ключами.

OAuth 2.0: Делегована авторизація

OAuth 2.0 — це не стратегія автентифікації у традиційному розумінні, а протокол делегованої авторизації (delegated authorization). Він дозволяє користувачеві надати стороннім застосункам обмежений доступ до своїх ресурсів на іншому сервісі без розкриття паролю. Найпоширеніший приклад — кнопка «Увійти через Google» або «Увійти через GitHub».

Концептуальна модель OAuth 2.0

Уявіть ситуацію: ви хочете дозволити сервісу друку фотографій (третя сторона) отримати доступ до ваших фотографій у хмарному сховищі (власник ресурсу — Google Drive). Традиційний підхід вимагав би, щоб ви повідомили сервісу друку свій пароль від Google. Це створює величезні ризики:

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

OAuth 2.0 вирішує цю проблему через токени доступу з обмеженою областю видимості (scoped access tokens). Замість паролю сервіс друку отримує токен, що дозволяє лише читати фотографії (scope: read:photos), але не змінювати налаштування облікового запису або читати електронну пошту.

Учасники протоколу

OAuth 2.0 визначає чотири ключові ролі:

👤 Resource Owner (Власник ресурсу)

Користувач, який володіє даними. Наприклад, ви — власник своїх фотографій у Google Drive.

🖥️ Client (Клієнт)

Сторонній застосунок, що запитує доступ до ресурсів від імені користувача. Наприклад, сервіс друку фотографій або ваш власний веб-застосунок, що дозволяє користувачам входити через Google.

🔐 Authorization Server (Сервер авторизації)

Сервер, що автентифікує користувача та видає токени доступу після отримання згоди. Наприклад, accounts.google.com або github.com/login/oauth.

📦 Resource Server (Сервер ресурсів)

API, що зберігає захищені ресурси користувача та приймає запити з токенами доступу. Наприклад, Google Drive API або GitHub API. Часто authorization server та resource server є частиною однієї системи.

Потік авторизації Authorization Code (найбезпечніший)

Розглянемо покроковий процес входу користувача через Google у ваш веб-застосунок:

Крок 1. Перенаправлення на сервер авторизації

Користувач натискає кнопку «Увійти через Google» у вашому застосунку. Frontend перенаправляє браузер на URL авторизації Google:

https://accounts.google.com/o/oauth2/v2/auth?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://yourapp.com/auth/callback&
  response_type=code&
  scope=openid%20email%20profile&
  state=random_string_for_csrf_protection

Параметри:

  • client_id — публічний ідентифікатор вашого застосунку, зареєстрований у Google Cloud Console.
  • redirect_uri — URL вашого застосунку, куди Google поверне користувача після авторизації.
  • response_type=code — запит на отримання authorization code (перший етап потоку).
  • scope — запитувані дозволи (email, profile, read:photos тощо).
  • state — випадковий рядок для захисту від CSRF атак (Cross-Site Request Forgery).

Крок 2. Автентифікація та згода користувача

Google відображає екран входу (якщо користувач ще не автентифікований) та екран згоди (consent screen), де перелічені запитувані дозволи:

Застосунок "YourApp" запитує дозвіл на:
✓ Перегляд вашої електронної пошти
✓ Перегляд основної інформації профілю

Користувач натискає «Дозволити» або «Відхилити».

Крок 3. Повернення authorization code

Якщо користувач погодився, Google перенаправляє браузер назад до вашого застосунку з тимчасовим authorization code:

https://yourapp.com/auth/callback?
  code=4/0AX4XfWh...authorization_code...&
  state=random_string_for_csrf_protection

Authorization code є короткоживучим (зазвичай 10–60 секунд) та одноразовим — його можна обміняти на токени лише один раз.

Крок 4. Обмін authorization code на токени

Ваш backend отримує authorization code та надсилає POST-запит до Google Token Endpoint з client secret (секретний ключ, що зберігається на сервері):

const response = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    code: authorizationCode,
    client_id: process.env.GOOGLE_CLIENT_ID,
    client_secret: process.env.GOOGLE_CLIENT_SECRET, // Секрет!
    redirect_uri: 'https://yourapp.com/auth/callback',
    grant_type: 'authorization_code',
  }),
});

const tokens = await response.json();
/*
{
  "access_token": "ya29.a0AfH6...",
  "expires_in": 3600,
  "refresh_token": "1//0gFz...",
  "scope": "openid email profile",
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIs..." // JWT з даними користувача
}
*/

Крок 5. Використання access token для доступу до API

Ваш backend може тепер викликати Google API від імені користувача:

const profileResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
  headers: { 'Authorization': `Bearer ${tokens.access_token}` },
});

const profile = await profileResponse.json();
/*
{
  "id": "1234567890",
  "email": "user@example.com",
  "verified_email": true,
  "name": "Іван Петренко",
  "picture": "https://lh3.googleusercontent.com/..."
}
*/
Чому authorization code, а не токен одразу? Потік Authorization Code передає токени через backend, а не через браузер. Це критично важливо для безпеки: client_secret ніколи не потрапляє у браузер (де його можна викрасти через DevTools або шкідливі розширення). Альтернативний потік Implicit Flow (передача токена через URL у браузері) вважається застарілим та небезпечним для сучасних застосунків.

Коли використовувати OAuth 2.0

OAuth 2.0 є оптимальним вибором у наступних сценаріях:

  • Інтеграція з соціальними мережами: «Увійти через Google/Facebook/GitHub» для спрощення реєстрації.
  • Доступ до сторонніх API: ваш застосунок потребує читати дані користувача з іншого сервісу (Google Drive, Dropbox, Spotify).
  • Мікросервісна архітектура: делегування автентифікації центральному Identity Provider замість дублювання логіки у кожному сервісі.
  • Mobile та Desktop застосунки: OAuth 2.0 має спеціальні потоки для нативних застосунків (PKCE — Proof Key for Code Exchange).
OAuth 2.0 не є протоколом автентифікації — він не визначає стандартного способу отримання інформації про користувача. Для цього існує розширення OpenID Connect (OIDC), що додає поверх OAuth 2.0 стандартизований id_token (JWT з claims про користувача) та /userinfo ендпоінт. Більшість сучасних провайдерів (Google, Microsoft, Auth0) реалізують саме OIDC, а не чистий OAuth 2.0.

Single Sign-On (SSO): Єдиний вхід для корпоративних систем

Single Sign-On (SSO) — це архітектурний патерн, що дозволяє користувачеві автентифікуватися один раз у центральній системі і отримати доступ до множини пов'язаних застосунків без повторного введення облікових даних. SSO є стандартом де-facto у корпоративних середовищах, де співробітники працюють з десятками внутрішніх сервісів.

Переваги SSO для організацій

👥 Покращений user experience

Співробітник входить один раз вранці (зазвичай через корпоративний логін Windows/macOS) і автоматично отримує доступ до всіх внутрішніх систем — CRM, HR-портал, корпоративна Wiki, система управління проєктами. Не потрібно запам'ятовувати десятки паролів.

🔒 Централізована політика безпеки

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

📊 Аудит та моніторинг

Всі спроби входу, відмови та сесії логуються у центральній системі (Identity Provider). Це спрощує виявлення підозрілої активності — наприклад, одночасні входи з різних IP-адрес або спроби перебору паролів.

⚙️ Спрощення інтеграції нових застосунків

Новий сервіс не потребує власної системи автентифікації — він делегує перевірку ідентичності центральному IdP через стандартні протоколи (SAML, OIDC). Це прискорює розробку та зменшує ризики вразливостей.

Протоколи SSO: SAML 2.0 vs OpenID Connect

Існують два основні протоколи для реалізації SSO:

SAML 2.0 (Security Assertion Markup Language) — застарілий, але широко використовуваний у корпоративному середовищі XML-базований протокол. Підтримується великими enterprise системами (Microsoft Active Directory, Okta, Ping Identity). Складний у налаштуванні, але перевірений часом.

OpenID Connect (OIDC) — сучасне розширення OAuth 2.0, що використовує JSON та REST API замість XML. Простіший у реалізації, краще підходить для веб-застосунків та мобільних клієнтів. Підтримується Google Workspace, Microsoft Azure AD, Auth0.

Для нових проєктів рекомендується OpenID Connect через його простоту та гнучкість. SAML 2.0 має сенс лише для інтеграції зі старими enterprise системами, що не підтримують OIDC.

Архітектура SSO на основі OIDC

Loading diagram...
sequenceDiagram
    participant U as User
    participant A1 as App 1
    participant A2 as App 2
    participant IdP as Identity Provider<br/>(Auth0, Okta)
    
    U->>A1: Спроба доступу
    A1->>IdP: Перенаправлення на /authorize
    IdP->>U: Форма входу (login + password)
    U->>IdP: Облікові дані
    IdP->>IdP: Створення SSO сесії
    IdP->>A1: Authorization code
    A1->>IdP: Обмін code на tokens
    IdP->>A1: id_token + access_token
    A1->>U: Доступ надано
    
    Note over U,IdP: Користувач вже автентифікований у IdP
    
    U->>A2: Спроба доступу до іншого застосунку
    A2->>IdP: Перенаправлення на /authorize
    IdP->>IdP: Виявлено активну SSO сесію
    IdP->>A2: Authorization code (без запиту паролю!)
    A2->>IdP: Обмін code на tokens
    IdP->>A2: id_token + access_token
    A2->>U: Доступ надано автоматично
    
    style U fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style IdP fill:#8b5cf6,stroke:#6d28d9,color:#ffffff
    style A1 fill:#10b981,stroke:#047857,color:#ffffff
    style A2 fill:#10b981,stroke:#047857,color:#ffffff

Ключовий момент: Identity Provider зберігає власну SSO сесію (зазвичай у cookie sso_session з терміном дії 8–12 годин). Коли користувач намагається увійти у другий застосунок, IdP виявляє активну сесію та автоматично видає authorization code без повторного запиту паролю.


Порівняльна таблиця стратегій

Для ефективного вибору стратегії автентифікації необхідно розуміти компроміси між безпекою, продуктивністю та складністю реалізації. Розглянемо систематичне порівняння підходів:

КритерійSession-BasedToken-Based (JWT)OAuth 2.0 + OIDCSSO (SAML/OIDC)
Збереження стануStateful (Redis/DB)StatelessStatelessStateful (у IdP)
Масштабування❌ Складне (потрібен Redis Cluster)✅ Горизонтальне без обмежень✅ Горизонтальне без обмежень⚠️ Залежить від IdP
Миттєве відкликання✅ Видалення з Redis❌ Потрібен blacklist⚠️ Залежить від реалізації✅ Централізоване у IdP
Продуктивність⚠️ Запит до Redis на кожен запит✅ Лише перевірка підпису⚠️ Додаткові запити до IdP⚠️ Додаткові запити до IdP
Безпека токенів✅ HttpOnly cookies❌ Вразливість до XSS⚠️ Залежить від зберігання✅ Централізоване управління
Мобільні застосунки❌ Немає нативних cookies✅ Природна інтеграція✅ Природна інтеграція✅ OIDC потоки для mobile
CORS складність❌ Потрібна точна конфігурація✅ Без проблем✅ Без проблем✅ Без проблем
Розмір запиту✅ 32–64 байти (session ID)❌ 500–1500 байтів (JWT)❌ 500–2000 байтів❌ 1000–3000 байтів
Складність реалізації✅ Проста (вбудована у фреймворки)⚠️ Середня (потрібна бібліотека)❌ Складна (протокол OAuth)❌ Дуже складна (enterprise)
Типові Use CasesТрадиційні веб-застосункиSPA, мобільні, мікросервісиІнтеграція з соціальними мережамиКорпоративні системи
Знак ✅ означає переваги, ❌ — недоліки або обмеження, ⚠️ — залежність від конкретної реалізації або додаткові умови.

Критерії вибору стратегії для вашого проєкту

Вибір стратегії автентифікації залежить від архітектурних вимог, масштабу системи та команди розробників. Розглянемо практичні сценарії:

Сценарій 1: Малий монолітний застосунок (стартап MVP)

Вимоги:

  • Команда 2–3 розробники, обмежений бюджет
  • Очікувана аудиторія: 1000–10000 користувачів
  • Традиційний веб-застосунок (server-side rendering)
  • Немає планів на мобільні застосунки у найближчі 6 місяців

Рекомендація: Session-Based автентифікація

Обґрунтування:

  • Простота реалізації — express-session або @nestjs/passport-local налаштовуються за 30 хвилин.
  • Немає необхідності у складних бібліотеках JWT або конфігурації ключів.
  • Для малої аудиторії Redis Cluster не потрібен — достатньо одного екземпляра Redis на виділеному сервері або керованого сервісу (AWS ElastiCache, Azure Redis).
  • Природна інтеграція з браузерами через cookies.
// Мінімальна конфігурація у NestJS
import * as session from 'express-session';
import * as RedisStore from 'connect-redis';

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: true,
      maxAge: 3600000, // 1 година
    },
  })
);

Сценарій 2: Single Page Application з планами на мобільні застосунки

Вимоги:

  • React/Vue/Angular frontend, REST API backend
  • Плани на iOS/Android застосунки через 3–6 місяців
  • Очікувана аудиторія: 50000–500000 користувачів
  • Необхідність горизонтального масштабування

Рекомендація: Token-Based (JWT) з Refresh Tokens

Обґрунтування:

  • JWT токени працюють однаково у веб-браузерах та мобільних застосунках.
  • Stateless архітектура дозволяє запускати десятки серверів без синхронізації стану.
  • Короткоживучі access tokens (15 хвилин) + довгоживучі refresh tokens (7 днів) забезпечують баланс між безпекою та зручністю.
  • Refresh tokens зберігаються у HttpOnly cookies або у захищеному сховищі мобільного застосунку (Keychain на iOS, Keystore на Android).
// Генерація пари токенів
const accessToken = sign({ sub: user.id, roles: user.roles }, JWT_SECRET, {
  expiresIn: '15m',
});

const refreshToken = sign({ sub: user.id }, REFRESH_SECRET, {
  expiresIn: '7d',
});

// Збереження refresh token у БД для можливості відкликання
await db.refreshTokens.create({
  token: hashSync(refreshToken, 10), // Зберігаємо хеш, не сам токен!
  userId: user.id,
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});

Сценарій 3: Інтеграція з Google/Facebook для швидкої реєстрації

Вимоги:

  • Зменшення тертя при реєстрації (conversion rate)
  • Користувачі не хочуть запам'ятовувати ще один пароль
  • Потрібен доступ до профілю Google/Facebook для персоналізації

Рекомендація: OAuth 2.0 + OIDC через Passport.js

Обґрунтування:

  • Бібліотека Passport.js надає готові стратегії для десятків провайдерів (Google, Facebook, GitHub, Twitter).
  • Користувач реєструється за 2 кліки без введення email/пароля.
  • Ваш застосунок не зберігає паролі — це зменшує відповідальність за витоки даних.
// Налаштування Google OAuth у NestJS
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor() {
    super({
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: 'https://yourapp.com/auth/google/callback',
      scope: ['email', 'profile'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: VerifyCallback
  ): Promise<any> {
    const { id, emails, displayName, photos } = profile;
    
    // Знайти або створити користувача у вашій БД
    const user = await this.usersService.findOrCreateGoogleUser({
      googleId: id,
      email: emails[0].value,
      name: displayName,
      picture: photos[0].value,
    });
    
    done(null, user);
  }
}

Сценарій 4: Корпоративна система з десятками внутрішніх сервісів

Вимоги:

  • 5000+ співробітників
  • 20+ внутрішніх застосунків (CRM, ERP, HR, документообіг)
  • Централізоване управління доступом
  • Інтеграція з Active Directory або LDAP

Рекомендація: Single Sign-On через Okta, Auth0 або Azure AD

Обґрунтування:

  • Співробітник входить один раз вранці через корпоративний логін.
  • Адміністратори керують доступом централізовано — звільнення співробітника миттєво блокує доступ до всіх систем.
  • Готові інтеграції з популярними enterprise системами (Salesforce, Jira, Confluence, Slack).
  • Підтримка багатофакторної автентифікації (SMS, TOTP, push-нотифікації).
Вартість enterprise SSO рішень: керовані сервіси на кшталт Okta або Auth0 коштують від $2–5 за користувача на місяць. Для великих організацій це може становити десятки тисяч доларів на рік. Альтернативою є самостійне розгортання open-source рішень (Keycloak, Authentik), але це вимагає виділених DevOps ресурсів для підтримки.

Інтерактивні запитання для самоперевірки


Ключові висновки

⚖️ Stateful vs Stateless

Session-based зберігає стан на сервері, забезпечує миттєве відкликання, але ускладнює масштабування. Token-based є stateless, ідеально масштабується горизонтально, але вимагає додаткових механізмів для відкликання токенів.

🔐 OAuth 2.0 для делегованого доступу

OAuth 2.0 дозволяє користувачам надавати обмежений доступ до своїх ресурсів стороннім застосункам без розкриття паролю. OpenID Connect розширює OAuth 2.0 стандартизованою автентифікацією через id_token.

🏢 SSO для корпоративних систем

Single Sign-On забезпечує єдиний вхід для десятків застосунків через центральний Identity Provider. Покращує user experience, спрощує управління доступом та аудит безпеки.

🎯 Немає універсального рішення

Вибір стратегії залежить від масштабу проєкту, платформ клієнтів (веб, мобільні, desktop), вимог до масштабування та доступних ресурсів команди. Для малих проєктів — session-based, для SPA та mobile — JWT, для корпоративних систем — SSO.

Рекомендовані ресурси для поглибленого вивчення

  • RFC 6749 — OAuth 2.0 Authorization Framework: офіційна специфікація протоколу OAuth 2.0, що визначає потоки авторизації, ролі учасників та формати токенів.
  • OpenID Connect Core 1.0: специфікація OIDC, що розширює OAuth 2.0 стандартизованою автентифікацією та id_token.
  • OWASP Session Management Cheat Sheet: найкращі практики безпечної реалізації серверних сесій, налаштування cookies та захист від атак.
  • JWT.io: інтерактивний інструмент для декодування, перевірки та створення JWT токенів з підтримкою різних алгоритмів підпису.
  • Passport.js Documentation: бібліотека для Node.js з готовими стратегіями автентифікації (Local, JWT, Google, Facebook, GitHub тощо).
У наступній лекції ми детально розглянемо практичну реалізацію session-based автентифікації у NestJS — інтеграцію express-session, налаштування Redis як сховища сесій, конфігурацію безпечних cookies та створення Guard для захисту маршрутів. Ви створите повноцінну систему входу, виходу та управління сесіями.
Copyright © 2026