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

Где будешь хранить JWT токены?

1.0 Junior🔥 191 комментариев
#Браузер и сетевые технологии

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

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

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

Хранилище JWT токенов: обзор подходов

Это критичный вопрос безопасности. Выбор места хранения JWT влияет на XSS и CSRF уязвимости. Разберем все варианты с плюсами и минусами.

Вариант 1: localStorage (НЕ рекомендуется)

// Сохранение
localStorage.setItem('accessToken', token);

// Использование
const token = localStorage.getItem('accessToken');
fetch('/api/data', {
  headers: { 'Authorization': `Bearer ${token}` }
});

Преимущества:

  • Простейшее решение
  • Работает с несколькими вкладками (через events)
  • Доступно всегда

Недостатки (критичные!):

  • XSS уязвимость: любой вредоносный скрипт может прочитать токен
  • Нет срока давности (если не реализовать сам)
  • Нет защиты от CSRF (нужно самостоятельно)
  • Синхронное чтение (блокирует UI)

Вердикт: Избегать для чувствительных данных.

Вариант 2: sessionStorage

// Сохранение
sessionStorage.setItem('accessToken', token);

// Использование
const token = sessionStorage.getItem('accessToken');

Преимущества:

  • Очищается при закрытии вкладки (хорошо для public компьютеров)
  • Изолирован по вкладкам

Недостатки:

  • Все те же XSS уязвимости как localStorage
  • Теряется при обновлении страницы
  • Нельзя использовать в разных вкладках

Вердикт: Не рекомендуется.

Вариант 3: Secure HttpOnly Cookie (РЕКОМЕНДУЕТСЯ)

Это наиболее безопасный подход. Токен хранится в cookies, которые:

  • Недоступны для JavaScript (HttpOnly флаг)
  • Отправляются автоматически с запросами (Secure флаг для HTTPS)
  • Защищены от CSRF (SameSite флаг)

Как это работает:

// На сервере (при логине)
res.setHeader('Set-Cookie', [
  `accessToken=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600`,
  `refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/refresh; Max-Age=604800`
]);

// На клиенте
fetch('/api/data', {
  credentials: 'include'
});

Флаги Cookie:

ФлагНазначение
HttpOnlyНедоступен из JavaScript
SecureОтправляется только по HTTPS
SameSite=StrictНе отправляется cross-site
Path=/apiДоступен только для этого пути
Max-AgeВремя жизни в секундах

Преимущества:

  • ✅ Защита от XSS
  • ✅ Защита от CSRF
  • ✅ Защита от MITM
  • ✅ Автоматическая отправка
  • ✅ Автоматическое удаление

Вердикт: Лучший вариант для большинства приложений.

Вариант 4: Hybrid подход (Best Practice)

Комбинируем HttpOnly Cookie + Memory:

class AuthStore {
  private accessToken: string | null = null;
  
  async login(credentials) {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials),
      credentials: 'include'
    });
    
    const data = await response.json();
    this.accessToken = data.accessToken || null;
  }
  
  async refreshToken() {
    const response = await fetch('/api/refresh', {
      method: 'POST',
      credentials: 'include'
    });
    
    const data = await response.json();
    this.accessToken = data.accessToken;
  }
  
  getAccessToken() {
    return this.accessToken;
  }
  
  logout() {
    this.accessToken = null;
    fetch('/api/logout', { credentials: 'include' });
  }
}

Flow:

  1. Access Token в HttpOnly Cookie (защита от XSS)
  2. Refresh Token в HttpOnly Cookie с SameSite=Strict
  3. Access Token копия в памяти (для локального использования)
  4. При обновлении страницы: доступ через Cookie
  5. Проверяем, не истек ли токен

Вариант 5: In-Memory (для критичных систем)

class SecureAuthStore {
  private accessToken: string | null = null;
  private refreshTimeout: NodeJS.Timeout | null = null;
  
  constructor() {
    this.refreshToken();
  }
  
  async refreshToken() {
    try {
      const response = await fetch('/api/refresh', {
        credentials: 'include'
      });
      
      const { accessToken } = await response.json();
      this.accessToken = accessToken;
      
      this.scheduleRefresh(55 * 60 * 1000);
    } catch (error) {
      this.logout();
    }
  }
  
  private scheduleRefresh(time: number) {
    if (this.refreshTimeout) clearTimeout(this.refreshTimeout);
    this.refreshTimeout = setTimeout(() => this.refreshToken(), time);
  }
  
  logout() {
    this.accessToken = null;
    if (this.refreshTimeout) clearTimeout(this.refreshTimeout);
  }
}

Преимущества:

  • ✅ Максимальная безопасность
  • ✅ Токен удаляется при закрытии вкладки
  • ✅ Защита от XSS

Недостатки:

  • ❌ При перезагрузке нужен новый токен
  • ❌ Возможна задержка при загрузке
  • ❌ Сложнее синхронизировать вкладки

Мой рекомендуемый подход

Для современных приложений:

// 1. Access Token: HttpOnly Secure Cookie
// 2. Refresh Token: HttpOnly Secure Cookie
// 3. Access Token копия: In-Memory
// 4. Все запросы с credentials: 'include'

fetch('/api/data', {
  credentials: 'include'
});

Конфигурация сервера:

res.cookie('accessToken', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  path: '/',
  maxAge: 15 * 60 * 1000
});

res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  path: '/api/auth/refresh',
  maxAge: 7 * 24 * 60 * 60 * 1000
});

Заключение

Избегать: localStorage, sessionStorage

Использовать: HttpOnly Secure Cookies + In-Memory

Ключ: Комбинируй несколько подходов для глубокой защиты. Hybrid подход обеспечивает баланс между безопасностью и удобством использования.

Где будешь хранить JWT токены? | PrepBro