Модуль 27

JWT-аутентифікація у FastAPI

Ось готовий урок, створений спеціально для тебе у стилі CS50. Вмикай уяву, ми починаємо!


🎓 Тема уроку: JWT-аутентифікація у FastAPI

(Або: Як пустити користувача в систему і не питати паспорт кожні 5 секунд)


1. 🔥 Вступ: Проблема та мотивація

Уявіть, що ви організовуєте величезний закритий музичний фестиваль. Тисячі людей. Охорона на вході сувора.

Ви приходите, показуєте паспорт, вас знаходять у списку, і ви заходите. Чудово. Але за 5 хвилин ви захотіли вийти купити води. Ви повертаєтесь, і охоронець знову каже: «Паспорт? Я маю знайти вас у списку». Ви йдете до туалету — знову «Паспорт?». Ви йдете до сцени — «Паспорт?».

Це незручно, правда? Це повільно. Це дратує.

У світі веб-розробки це класична проблема HTTP. HTTP — це протокол без стану (stateless). Це означає, що сервер має амнезію. Він не пам’ятає, що ви залогінилися 2 секунди тому. Кожен ваш запит для нього — як перший раз.

Тож, як нам вирішити цю проблему, не змушуючи користувача вводити логін і пароль при кожному кліку?

Повернемось до фестивалю. Що роблять організатори? Вони один раз перевіряють паспорт і чіпляють вам на руку браслет. Тепер охоронцю не треба список гостей. Він просто дивиться на браслет: 1. Він справжній? (Не підробка). 2. Він цілий? (Термін дії). 3. Він червоний чи зелений? (Доступ у VIP-зону чи ні).

JWT (JSON Web Token) — це і є цей цифровий браслет. Без нього ваш API або буде "дірявим" (пускати всіх), або "параноїдальним" (вимагати пароль щоразу). Сьогодні ми навчимо FastAPI видавати і перевіряти ці браслети.


2. 🧠 Теоретична база (Що всередині браслета?)

Давайте розберемо цей "браслет" на атоми. JWT — це довгий рядок символів, який виглядає як абракадабра, але насправді складається з трьох частин, розділених крапками: AAAAA.BBBBB.CCCCC

1. Header (Заголовок) — Червона частина

Тут написано: "Привіт, я токен типу JWT, зашифрований алгоритмом HS256". Це метадані.

2. Payload (Корисне навантаження) — Фіолетова частина

Це найважливіше. Тут лежать дані про користувача (Claims). Наприклад: { "sub": "user123", "role": "admin", "exp": 1715000000 }.

Увага! Ця частина закодована (Base64), але НЕ зашифрована. Будь-хто може її прочитати. 💡 Інтуїтивне правило: Ніколи не кладіть сюди паролі чи номери карток. Це як написати пін-код на прозорому бейджі.

3. Signature (Підпис) — Синя частина

Це "сургучева печатка". Сервер бере перші дві частини, додає до них Секретний Ключ (Secret Key), який знає тільки він, і перемішує це в кашу (хешує). Якщо хакер спробує змінити в Payload роль з "user" на "admin", підпис перестане збігатися, і сервер скаже: "Фейк! Цей браслет підроблений!".

Як це працює у FastAPI?

Ми не пишемо це вручну. Ми використовуємо "магію" Dependency Injection (Depends). 1. Користувач шле username + password -> /login. 2. Якщо ок -> Сервер генерує JWT і віддає його. 3. Користувач шле запит до захищеного ресурсу і додає заголовок: Authorization: Bearer <TOKEN>. 4. FastAPI ловить токен, перевіряє Підпис і пускає (або ні).


3. 🧪 Приклади (Кодимо!)

Спочатку підготуємо ґрунт. Нам потрібні бібліотеки для роботи з JWT та безпекою. У терміналі:

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart

Крок 1: Мінімальна генерація токена

Давайте просто створимо токен. Ще без сервера, просто логіку.

from jose import jwt
from datetime import datetime, timedelta

# СЕКРЕТНИЙ КЛЮЧ - це як ключ від сейфа. Нікому не показуйте!
SECRET_KEY = "super-secret-key-shhhhh"
ALGORITHM = "HS256"

def create_token(data: dict):
    to_encode = data.copy()
    # Додаємо час життя токена (наприклад, 30 хвилин)
    expire = datetime.utcnow() + timedelta(minutes=30)
    to_encode.update({"exp": expire})

    # Створюємо підпис
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Тест
my_token = create_token({"sub": "david_malan", "role": "teacher"})
print(f"Ваш токен: {my_token}")

Запустіть це. Ви побачите довгий рядок. Це і є ваш пропуск.


Крок 2: Реальний FastAPI додаток (Вхід і перевірка)

А тепер — повний цикл. Створимо ендпоінт для входу і захищений ендпоінт.

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt

app = FastAPI()

SECRET_KEY = "my_secret_key"
ALGORITHM = "HS256"

# Ця штука каже FastAPI: "Шукай токен у заголовку Authorization"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# 1. Функція перевірки токена (Охоронець)
def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="Invalid token")
        return username
    except JWTError:
        raise HTTPException(status_code=401, detail="Could not validate credentials")

# 2. Ендпоінт логіну (Видача браслета)
@app.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # У реальному житті тут перевірка в базі даних!
    if form_data.username != "admin" or form_data.password != "password":
        raise HTTPException(status_code=400, detail="Incorrect username or password")

    # Генеруємо токен
    token_data = {"sub": form_data.username}
    token = jwt.encode(token_data, SECRET_KEY, algorithm=ALGORITHM)

    return {"access_token": token, "token_type": "bearer"}

# 3. Захищений ендпоінт (VIP зона)
@app.get("/users/me")
def read_users_me(current_user: str = Depends(get_current_user)):
    return {"message": f"Ласкаво просимо до VIP-зони, {current_user}!"}

Як це протестувати? 1. Відкрийте Swagger UI (http://127.0.0.1:8000/docs). 2. Натисніть кнопку Authorize (зелений замочок). 3. Введіть admin і password. Натисніть Login. 4. FastAPI отримає токен і збереже його в пам'яті браузера. 5. Тепер спробуйте виконати запит /users/me. Працює! 6. Спробуйте виконати /users/me БЕЗ авторизації (Logout). Отримаєте "Not authenticated".


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

Час забруднити руки. Просто читати код — це як дивитися на турнік і думати, що стаєш сильнішим.

Завдання 1: "Хакери не сплять" Запустіть код з прикладу. Отримайте токен. Скопіюйте його. Тепер підіть на сайт jwt.io і вставте токен туди. Питання: Що ви бачите праворуч? Чи бачите ви свій юзернейм? (Це доказ, що Payload не зашифрований).

Завдання 2: "Зміна секрету" Змініть SECRET_KEY у коді на "new-secret", перезапустіть сервер, але спробуйте використати старий токен (який ви отримали до перезапуску) через Swagger або Postman. Очікування: Що відповість сервер? Чому? (Підказка: підпис більше не валідний).

Завдання 3: "Час вийшов" Додайте у створення токена параметр exp (час життя), який дорівнює 10 секундам від поточного часу. Згенеруйте токен, швидко перевірте доступ — працює. Зачекайте 11 секунд. Спробуйте знову. Результат: Ви маєте отримати помилку про прострочений токен (ExpiredSignatureError).

Завдання 4: "Рольова гра" Додайте в payload поле "role": "admin". Створіть новий ендпоінт @app.get("/admin-zone"). Всередині нього перевіряйте: якщо role у токені не дорівнює "admin", викидайте помилку 403 Forbidden ("Вам сюди не можна").


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

Ось де ми переходимо від "новачка, який копіює код" до "інженера".

Типова помилка №1: Зберігання всього в токені. Новачок: Запхає в токен всю історію покупок юзера і його адресу. Профі: Токен стає гігантським і гальмує кожен запит. У токені — тільки ID (sub) і, можливо, роль. Решту тягнемо з бази за ID.

Типова помилка №2: Хардкод секретного ключа. Новачок: SECRET_KEY = "my_secret" прямо в коді і заливає це на GitHub. Профі: Використовує змінні оточення (.env файл). Якщо хакер отримає ваш код, він не зможе підробити токени без ключа з .env.

Як думає Архітектор: "Мені не треба зберігати сесії в Redis або базі даних. Якщо у мене буде 1 мільйон користувачів і 50 серверів, JWT працюватиме ідеально, бо будь-який сервер може математично перевірити підпис, не звертаючись до центральної бази сесій. Це масштабованість".


6. 🧩 Підсумок

Що ми сьогодні зробили? 1. Зрозуміли, що HTTP не має пам'яті, і нам потрібен механізм ідентифікації. 2. Розібрали JWT як цифровий браслет фестивалю: видно всім, але підробити неможливо. 3. Навчили FastAPI видавати ці браслети і перевіряти їх при вході у VIP-зону.

Тепер ви вмієте: ✅ Захищати свої API від неавторизованого доступу. ✅ Передавати дані користувача безпечно (якщо це не паролі!). ✅ Працювати з Dependency Injection для auth-логіки.

🔜 Тизер наступного уроку: Зараз ми перевіряли юзера admin / password, які були просто "вшиті" в код. Але у реальному житті користувачі реєструються, змінюють паролі, забувають їх. Наступного разу ми підключимо справжню базу даних (PostgreSQL) і навчимося хешувати паролі, щоб навіть якщо базу вкрадуть, паролі залишилися таємницею.

Це був CS50. Побачимось!