← Назад к вопросам

Что такое Refresh Token?

1.7 Middle🔥 201 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Refresh Token — обновление аутентификации

Refresh Token — это специальный токен, который используется для получения нового Access Token без повторной аутентификации пользователя. Это двухуровневый механизм безопасности, часто применяемый в современных API и веб-приложениях.

Зачем нужны два токена?

Access Token:

  • Короткоживущий (15 минут - 1 час)
  • Используется для каждого запроса к API
  • Если скомпрометирован — ущерб ограничен по времени

Refresh Token:

  • Долгоживущий (дни, недели, месяцы)
  • Хранится безопасно (например, в httpOnly cookie)
  • Используется ТОЛЬКО для получения нового Access Token

Поток работы (OAuth 2.0)

1. Пользователь вводит логин/пароль
2. Сервер выдаёт: Access Token + Refresh Token
3. Клиент использует Access Token для каждого запроса
4. Когда Access Token истекает → используем Refresh Token
5. Сервер выдаёт новый Access Token (без повторной аутентификации)

Практическая реализация на Python (FastAPI + JWT)

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer
from datetime import datetime, timedelta, UTC
import jwt
from pydantic import BaseModel

app = FastAPI()
security = HTTPBearer()

# Конфигурация
SECRET_KEY = "your-secret-key"
ACCESS_TOKEN_EXPIRE = timedelta(minutes=15)
REFRESH_TOKEN_EXPIRE = timedelta(days=7)
ALGORITHM = "HS256"

class TokenResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str

def create_access_token(user_id: str) -> str:
    """Создание короткоживущего Access Token"""
    payload = {
        "user_id": user_id,
        "exp": datetime.now(UTC) + ACCESS_TOKEN_EXPIRE,
        "type": "access"
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def create_refresh_token(user_id: str) -> str:
    """Создание долгоживущего Refresh Token"""
    payload = {
        "user_id": user_id,
        "exp": datetime.now(UTC) + REFRESH_TOKEN_EXPIRE,
        "type": "refresh"
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

@app.post("/login")
def login(username: str, password: str) -> TokenResponse:
    """Вход — выдача обоих токенов"""
    # Проверка учётных данных
    user_id = "user123"  # Результат проверки
    
    return TokenResponse(
        access_token=create_access_token(user_id),
        refresh_token=create_refresh_token(user_id),
        token_type="bearer"
    )

@app.post("/refresh")
def refresh_token(token: str) -> TokenResponse:
    """Обновление Access Token с помощью Refresh Token"""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        
        # Проверяем тип токена
        if payload.get("type") != "refresh":
            raise HTTPException(status_code=401, detail="Invalid token type")
        
        user_id = payload.get("user_id")
        if not user_id:
            raise HTTPException(status_code=401, detail="Invalid token")
        
        # Выдаём новый Access Token
        return TokenResponse(
            access_token=create_access_token(user_id),
            refresh_token=token,  # Refresh Token остаётся прежним
            token_type="bearer"
        )
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Refresh token expired")
    except jwt.DecodeError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/protected")
def protected_route(token: str = Depends(security)) -> dict:
    """Защищённый эндпойнт — требует валидный Access Token"""
    try:
        payload = jwt.decode(token.credentials, SECRET_KEY, algorithms=[ALGORITHM])
        
        if payload.get("type") != "access":
            raise HTTPException(status_code=401, detail="Invalid token type")
        
        return {"message": f"Hello, user {payload[user_id]}"}
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Access token expired")
    except jwt.DecodeError:
        raise HTTPException(status_code=401, detail="Invalid token")

Безопасность и best practices

# 1. Хранение в httpOnly cookie (защита от XSS)
from fastapi.responses import JSONResponse

response = JSONResponse({"access_token": access_token})
response.set_cookie(
    "refresh_token",
    refresh_token,
    httponly=True,  # Недоступен JavaScript
    secure=True,    # Только HTTPS
    samesite="strict"  # Защита от CSRF
)
return response

# 2. Блокирование (revocation) токенов
# Можно использовать Redis для хранения чёрных списков
blacklist = set()

def logout(token: str):
    """Добавить токен в чёрный список"""
    blacklist.add(token)

def is_token_blacklisted(token: str) -> bool:
    return token in blacklist

# 3. Ротация Refresh Token (для дополнительной безопасности)
# При каждом обновлении выдаём новый Refresh Token

Ключевые моменты

  • Access Token — короткоживущий, для каждого запроса
  • Refresh Token — долгоживущий, для получения нового Access
  • Разделение — уменьшает окно компрометации
  • httpOnly cookies — хранение Refresh Token безопаснее localStorage
  • Ротация — выдача нового Refresh при обновлении
  • Revocation — возможность отозвать токены (logout)
Что такое Refresh Token? | PrepBro