Зачем хранить JWT в Cookie?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем Хранить JWT в Cookie
Хранение JWT (JSON Web Token) в cookie — это практика безопасности, которая имеет как преимущества, так и недостатки. Это альтернатива хранению JWT в localStorage или sessionStorage браузера.
Что Такое JWT
JWT — это компактный, URL-безопасный способ передачи информации между клиентом и сервером в виде JSON объекта. JWT состоит из трех частей, разделенных точками:
header.payload.signature
Пример:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Места Хранения JWT
1. localStorage
// Сохранение
localStorage.setItem('token', jwtToken);
// Использование в запросах
const headers = {
'Authorization': `Bearer ${localStorage.getItem('token')}`
};
fetch('/api/data', { headers });
Проблемы:
- Уязвим к XSS (Cross-Site Scripting) атакам
- JavaScript код может украсть токен
- Нет защиты от CSRF
2. sessionStorage
sessionStorage.setItem('token', jwtToken);
Проблемы:
- Те же XSS уязвимости
- Очищается при закрытии браузера
3. Cookie (более безопасно)
# На backend (Python/FastAPI)
from fastapi.responses import JSONResponse
@app.post("/login")
async def login(credentials):
user = authenticate_user(credentials)
token = create_jwt_token(user)
response = JSONResponse({"status": "success"})
response.set_cookie(
key="access_token",
value=token,
httponly=True, # защита от XSS
secure=True, # только HTTPS
samesite="Strict", # защита от CSRF
max_age=3600
)
return response
Преимущества Хранения JWT в Cookie
1. Защита от XSS (Cross-Site Scripting)
# httponly=True предотвращает доступ из JavaScript
response.set_cookie(
key="access_token",
value=token,
httponly=True # JavaScript НЕ может получить доступ
)
JavaScript не может украсть токен:
// Это НЕ сработает
const token = document.cookie; // пустая строка для httponly cookies
console.log(localStorage.getItem('token')); // токен украден!
2. Автоматическая Отправка с Каждым Запросом
Browser автоматически отправляет cookie с каждым запросом к серверу:
# На backend
@app.get("/api/user")
async def get_user(request: Request):
token = request.cookies.get("access_token")
user = verify_token(token)
return user
// На frontend — не нужно добавлять токен вручную
fetch('/api/user', {
method: 'GET',
credentials: 'include' // отправить cookies
});
// Браузер автоматически отправит cookie
3. Защита от CSRF (Cross-Site Request Forgery)
С правильными настройками cookie:
response.set_cookie(
key="access_token",
value=token,
httponly=True,
secure=True,
samesite="Strict" # защита от CSRF
)
Как это работает:
samesite="Strict"— cookie отправляется только запросам с того же сайта- Предотвращает отправку cookie с cross-site запросов
4. Стандартный HTTP Протокол
Cookie — это часть HTTP протокола, поддерживаемая всеми браузерами:
HTTP/1.1 200 OK
Set-Cookie: access_token=eyJ...; HttpOnly; Secure; SameSite=Strict
Content-Type: application/json
{"status": "logged in"}
Недостатки Хранения JWT в Cookie
1. Сложнее Работать с Multiple Domains
Cookie привязаны к domain:
# Проблема: API на другом домене
# Frontend: example.com
# API: api.example.com
# Решение: использовать CORS с правильными настройками
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True, # разрешить отправку cookies
)
2. Проблемы с Mobile Apps
Native мобильные приложения не имеют встроенной поддержки cookie:
// iOS — нужно обрабатывать cookies вручную
let config = URLSessionConfiguration.default
config.httpShouldSetCookies = true
let session = URLSession(configuration: config)
// Или использовать localStorage для мобильных
UserDefaults.standard.set(token, forKey: "access_token")
3. Logout Сложнее
Делать logout с cookie требует участия сервера:
@app.post("/logout")
async def logout(response: Response):
response.delete_cookie("access_token")
return {"status": "logged out"}
Полный Пример: Безопасная Аутентификация
Backend (Python/FastAPI)
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from datetime import datetime, timedelta
import jwt
from passlib.context import CryptContext
app = FastAPI()
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=["bcrypt"])
@app.post("/login")
async def login(credentials: dict):
user = authenticate_user(credentials["email"], credentials["password"])
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
# Создать JWT token
payload = {
"sub": user["id"],
"email": user["email"],
"exp": datetime.utcnow() + timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
# Сохранить в cookie
response = JSONResponse({"status": "success"})
response.set_cookie(
key="access_token",
value=token,
httponly=True, # защита от XSS
secure=True, # только HTTPS
samesite="Strict", # защита от CSRF
max_age=3600 # 1 час
)
return response
@app.get("/api/user")
async def get_user(request: Request):
token = request.cookies.get("access_token")
if not token:
raise HTTPException(status_code=401, detail="Not authenticated")
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
user = get_user_by_id(user_id)
return user
@app.post("/logout")
async def logout(response: Response):
response.delete_cookie("access_token")
return {"status": "logged out"}
Frontend (JavaScript)
// Логин
async function login(email, password) {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // отправить cookies
body: JSON.stringify({ email, password })
});
if (response.ok) {
console.log('Logged in!');
// Токен в cookie, доступен только серверу
}
}
// Получить данные пользователя
async function getUser() {
const response = await fetch('https://api.example.com/api/user', {
method: 'GET',
credentials: 'include' // браузер отправит cookie
});
const user = await response.json();
return user;
}
// Logout
async function logout() {
const response = await fetch('https://api.example.com/logout', {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
console.log('Logged out!');
// Cookie удалена
}
}
Сравнение Подходов
| Аспект | localStorage | Cookie (httponly) |
|---|---|---|
| XSS защита | Нет | Да |
| CSRF защита | Нет | Да (с samesite) |
| Автоматическая отправка | Нет | Да |
| Доступ из JavaScript | Да (опасно) | Нет (безопасно) |
| Multi-domain | Проще | Сложнее |
| Mobile apps | Проще | Сложнее |
| Logout | Клиент | Сервер |
Лучшие Практики
- Всегда используй HTTPS для защиты от перехвата
- Установи httponly = True для защиты от XSS
- Установи secure = True для HTTPS-only
- Установи samesite = "Strict" для защиты от CSRF
- Установи short expiration (1-15 минут для access токена)
- Используй refresh токены для долгосрочной аутентификации
- Регулярно ротируй ключи подписи токенов
Заключение
Хранение JWT в cookie — это более безопасный подход чем localStorage, благодаря защите от XSS и CSRF атак. Cookie автоматически отправляются с каждым запросом и недоступны для JavaScript кода. Однако этот подход требует правильной конфигурации CORS и может быть сложнее для мобильных приложений. Выбирайте метод в зависимости от архитектуры вашего приложения и требований безопасности.