Ось готовий урок, створений спеціально для тебе у стилі 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. Побачимось!