JavaRush /Курси /Модуль 4: FastAPI /Налаштування рефреш-токенів для оновлення доступу

Налаштування рефреш-токенів для оновлення доступу

Модуль 4: FastAPI
Рівень 4 , Лекція 7
Відкрита

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

рефреш-токен приходить на допомогу. Основні його переваги:

  • Довгострокове зберігання: рефреш-токен має довший термін дії.
  • Оновлення доступу: дозволяє користувачу отримати новий Access-токен без повторного вводу даних.
  • Відновлення сесій: робить користувацький досвід більш плавним.

Рефреш-токени — це як ключ від дому, захований під килимком. Ми знаємо, що він там є, але користуємося ним тільки у разі потреби. Головне — щоб килимок ніхто не вкрав.


Реалізація рефреш-токенів у FastAPI

Тепер, коли ми розібралися, навіщо потрібні рефреш-токени, перейдемо до практики.

Отже, головна функція рефреш-токена — це оновлення Access-токена. Для цього ми створимо новий ендпоінт /token/refresh, який буде приймати рефреш-токен, перевіряти його валідність і, якщо все добре, видавати новий Access-токен.

Почнемо з основного коду:


from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from datetime import datetime, timedelta
from pydantic import BaseModel

app = FastAPI()

# Секрети та алгоритми для токенів
SECRET_KEY = "your_secret_key"
REFRESH_SECRET_KEY = "your_refresh_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 7

# OAuth2 схема
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Модель для токенів
class Token(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str

# Модель для даних користувача
class User(BaseModel):
    username: str

# Тимчасове сховище користувачів і рефреш-токенів
users_db = {
    "johndoe": {"username": "johndoe", "hashed_password": "fakehashedpassword"}
}
refresh_tokens_store = {}

# Генерація Access-токена
def create_access_token(data: dict, expires_delta: timedelta):
    to_encode = data.copy()
    expire = datetime.utcnow() + expires_delta
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Генерація рефреш-токена
def create_refresh_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, REFRESH_SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Вхід користувача і генерація токенів
@app.post("/token", response_model=Token)
async def login(username: str, password: str):
    user = users_db.get(username)
    if not user or password != "password":  # Приклад перевірки, краще використовувати хеш
        raise HTTPException(status_code=400, detail="Invalid credentials")

    access_token = create_access_token(
        data={"sub": user["username"]}, 
        expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    )
    refresh_token = create_refresh_token(data={"sub": user["username"]})
    refresh_tokens_store[user["username"]] = refresh_token

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

# Оновлення Access-токена
@app.post("/token/refresh", response_model=Token)
async def refresh_token(refresh_token: str):
    try:
        # Перевірка рефреш-токена
        payload = jwt.decode(refresh_token, REFRESH_SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None or refresh_tokens_store.get(username) != refresh_token:
            raise HTTPException(status_code=401, detail="Invalid refresh token")

        # Якщо все добре, створюємо новий Access-токен
        access_token = create_access_token(
            data={"sub": username}, 
            expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        )
        return {"access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer"}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")
  1. Сховище рефреш-токенів:
    У цьому прикладі ми зберігаємо рефреш-токени в пам'яті (refresh_tokens_store). Це не найкращий підхід, оскільки в реальних застосунках рефреш-токени слід зберігати безпечно (наприклад, у базі даних).
  2. Створення токенів:
    Ми розділили генерацію access_token і refresh_token, використовуючи різні секретні ключі (SECRET_KEY і REFRESH_SECRET_KEY). Це підвищує безпеку системи.
  3. Ендпоінт /token/refresh:
    Перевіряє валідність рефреш-токена.
    Генерує новий Access-токен, використовуючи ту ж інформацію про користувача.
  4. Термін дії токенів:
    Access-токен живе 15 хвилин, тоді як рефреш-токен — цілих 7 днів.

Практичні зауваження

  1. Безпечне зберігання токенів:
    • Access-токен може бути збережений в пам'яті клієнта (наприклад, у localStorage або sessionStorage).
    • Рефреш-токен повинен зберігатися тільки в HttpOnly cookie. Це захистить його від атак XSS.
  2. Закінчення терміну дії рефреш-токена:
    Після закінчення терміну дії рефреш-токена користувач буде змушений увійти в систему знову.
  3. Двофакторна аутентифікація:
    Включення рефреш-токенів дозволяє легко впроваджувати двофакторну аутентифікацію.

Типові помилки

Однією з поширених помилок є використання одного й того самого секретного ключа для Access і Refresh-токенів. Це підвищує ризик компрометації системи, оскільки зловмисник, отримавши доступ до ключа, зможе створювати обидва типи токенів.


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

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ