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

Что происходит с Access Token при изменение Refresh Token?

3.0 Senior🔥 111 комментариев
#REST API и HTTP#Безопасность

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

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

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

Взаимоотношение Access Token и Refresh Token

Это часто задаваемый вопрос на собеседованиях. Важно понимать, что Access Token и Refresh Token независимы друг от друга.

Сценарий: Пользователь обновляет Refresh Token

Когда пользователь логинится, сервер выдаёт пару токенов:

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "expires_in": 900,
  "token_type": "Bearer"
}

Что происходит с Access Token

Ответ: НИЧЕГО НЕ ПРОИСХОДИТ. Access Token остаётся неизменным, пока не истечёт срок его действия.

Почему они независимы?

  1. Разные сроки жизни

    • Access Token: 15 минут (короткий)
    • Refresh Token: 30 дней или больше (длинный)
  2. Разные предназначения

    • Access Token: используется в каждом запросе для аутентификации
    • Refresh Token: используется ТОЛЬКО для получения нового Access Token
  3. Разные место хранения

    • Access Token: в памяти или localStorage (фронтенд)
    • Refresh Token: в httpOnly cookie или защищённом хранилище

Пример из кода (FastAPI + JWT)

from fastapi import FastAPI, Depends
from jose import JWTError, jwt
from datetime import datetime, timedelta, timezone

app = FastAPI()

# Конфиги
ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 30
SECRET_KEY = "your-secret-key"

def create_access_token(data: dict) -> str:
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode["exp"] = expire
    to_encode["type"] = "access"
    encoded = jwt.encode(to_encode, SECRET_KEY, algorithm="HS256")
    return encoded

def create_refresh_token(data: dict) -> str:
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode["exp"] = expire
    to_encode["type"] = "refresh"
    encoded = jwt.encode(to_encode, SECRET_KEY, algorithm="HS256")
    return encoded

@app.post("/login")
async def login(username: str, password: str):
    # Проверяем учётные данные
    if verify_credentials(username, password):
        user_id = get_user_id(username)
        access_token = create_access_token({"sub": user_id})
        refresh_token = create_refresh_token({"sub": user_id})
        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "Bearer"
        }

@app.post("/refresh")
async def refresh(refresh_token: str):
    try:
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
        if payload.get("type") != "refresh":
            raise JWTError("Invalid token type")
        
        user_id = payload.get("sub")
        
        # ВАЖНО: старый Access Token остаётся в памяти клиента
        # Мы создаём НОВЫЙ Access Token
        new_access_token = create_access_token({"sub": user_id})
        
        return {
            "access_token": new_access_token,
            "token_type": "Bearer"
        }
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid refresh token")

Жизненный цикл токенов

Временная шкала:

0 мин  — Пользователь логинится
        → Выдан Access Token (истекает через 15 мин)
        → Выдан Refresh Token (истекает через 30 дней)

14 мин — Клиент делает запрос с Access Token
        → Запрос успешен
        → Access Token НЕ изменяется

16 мин — Access Token истёк
        → Клиент отправляет Refresh Token на /refresh
        → Сервер создаёт НОВЫЙ Access Token (срок 15 мин)
        → Старый Access Token уже в памяти, его просто не используем
        → Refresh Token может остаться прежним или обновиться (зависит от реализации)

30 дней — Refresh Token истёк
        → Клиент должен снова залогиниться

Что может измениться?

Можно обновить Refresh Token одновременно с Access Token (rotation strategy)

@app.post("/refresh")
async def refresh(refresh_token: str):
    payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
    user_id = payload.get("sub")
    
    # Выдаём оба новых токена
    new_access_token = create_access_token({"sub": user_id})
    new_refresh_token = create_refresh_token({"sub": user_id})
    
    # Опционально: инвалидируем старый refresh token в БД
    invalidate_refresh_token(refresh_token)
    
    return {
        "access_token": new_access_token,
        "refresh_token": new_refresh_token,
        "token_type": "Bearer"
    }

Частые ошибки разработчиков

❌ Инвалидировать Access Token при обновлении Refresh Token ❌ Хранить Refresh Token в localStorage ❌ Выдавать один токен вместо двух ❌ Не проверять тип токена (access vs refresh)

✅ Хранить Refresh Token в httpOnly cookie ✅ Проверять срок действия токенов ✅ Реализовать rotation (обновление refresh token) ✅ Добавить механизм blacklist для инвалидации

Итоговый ответ

При изменении (обновлении) Refresh Token с Access Token обычно ничего не происходит — он продолжает работать до истечения срока. Однако в некоторых реализациях (rotation strategy) оба токена обновляются одновременно.

Что происходит с Access Token при изменение Refresh Token? | PrepBro