Где будешь хранить JWT токены?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Хранилище 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:
- Access Token в HttpOnly Cookie (защита от XSS)
- Refresh Token в HttpOnly Cookie с SameSite=Strict
- Access Token копия в памяти (для локального использования)
- При обновлении страницы: доступ через Cookie
- Проверяем, не истек ли токен
Вариант 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 подход обеспечивает баланс между безопасностью и удобством использования.