Для початку — трохи теорії. 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")
- Сховище рефреш-токенів:
У цьому прикладі ми зберігаємо рефреш-токени в пам'яті (refresh_tokens_store). Це не найкращий підхід, оскільки в реальних застосунках рефреш-токени слід зберігати безпечно (наприклад, у базі даних). - Створення токенів:
Ми розділили генераціюaccess_tokenіrefresh_token, використовуючи різні секретні ключі (SECRET_KEYіREFRESH_SECRET_KEY). Це підвищує безпеку системи. - Ендпоінт
/token/refresh:
Перевіряє валідність рефреш-токена.
Генерує новий Access-токен, використовуючи ту ж інформацію про користувача. - Термін дії токенів:
Access-токен живе 15 хвилин, тоді як рефреш-токен — цілих 7 днів.
Практичні зауваження
- Безпечне зберігання токенів:
- Access-токен може бути збережений в пам'яті клієнта (наприклад, у
localStorageабоsessionStorage). - Рефреш-токен повинен зберігатися тільки в
HttpOnlycookie. Це захистить його від атак XSS.
- Access-токен може бути збережений в пам'яті клієнта (наприклад, у
- Закінчення терміну дії рефреш-токена:
Після закінчення терміну дії рефреш-токена користувач буде змушений увійти в систему знову. - Двофакторна аутентифікація:
Включення рефреш-токенів дозволяє легко впроваджувати двофакторну аутентифікацію.
Типові помилки
Однією з поширених помилок є використання одного й того самого секретного ключа для Access і Refresh-токенів. Це підвищує ризик компрометації системи, оскільки зловмисник, отримавши доступ до ключа, зможе створювати обидва типи токенів.
Тепер ваша система авторизації в FastAPI стала ще ближчою до бойової реальності: користувачі не будуть розлогінюватися кожні 15 хвилин, що зробить їхню взаємодію з застосунком більш плавною, а безпека мережі залишиться на високому рівні!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ