29 сент. 2025 г.·6 мин

Управление сессиями для веб‑приложений: cookies vs JWT vs refresh

Сравнение управления сессиями в веб-приложениях: cookie-сессии, JWT и refresh-токены с учётом моделей угроз и требований к выходу.

Управление сессиями для веб‑приложений: cookies vs JWT vs refresh

Что на самом деле делает управление сессиями

Сессия — это способ, которым ваше приложение отвечает на один вопрос после входа пользователя: «Кто вы прямо сейчас?» Когда этот ответ надёжен, приложение решает, что пользователь может видеть, что изменять и какие действия блокировать.

«Оставаться в системе» — это тоже выбор по безопасности. Вы решаете, как долго личность пользователя остаётся действительной, где хранится подтверждение личности и что происходит, если это подтверждение копируют.

Большинство веб-приложений опираются на три строительных блока:

  • Серверные сессии на cookie: браузер хранит cookie, а сервер при каждом запросе смотрит сессию в своей базе.\n- JWT access-токены: клиент посылает подписанный токен, который сервер может проверить без обращения в базу.\n- Refresh-токены: более долгоживущий секрет, используемый для получения новых короткоживущих access-токенов.

Это не столько конкурирующие «стили», сколько разные способы решать одни и те же компромиссы: скорость против контроля, простота против гибкости и «можно ли отозвать это прямо сейчас?» против «истечёт ли это само?».

Полезный критерий: если злоумышленник украдёт то, чем вы подтверждаете сессию (cookie или токен), что он сможет сделать и как долго. Серверные cookie-сессии часто выигрывают, когда нужен строгий контроль со стороны сервера — мгновенный выход или принудительная блокировка. JWT хорошо подходят для статeless-проверок между сервисами, но проблемны, когда нужна мгновенная аннулизация.

Нет единственного правильного варианта. Выбор зависит от вашей модели угроз, требований к выходу и сложности, которую команда готова поддерживать.

Модели угроз, которые меняют ответ

Хороший дизайн сессий зависит не от «лучшего» типа токена, а от того, против каких атак вы хотите защититься.

Если злоумышленник получает данные из браузерного хранилища (например, localStorage), JWT access-токены легко украсть, потому что JavaScript на странице может их прочитать. Украденное cookie отличается: если оно помечено как HttpOnly, обычный код страницы не может его прочитать, и простые атаки «снять токен» усложняются. Но если у атакующего есть доступ к устройству (потерянный ноут, вредоносное ПО, общий компьютер), cookies всё ещё можно скопировать из профиля браузера.

XSS (вредоносный код в вашей странице) меняет всё: атакующий может не нужно ничего воровать — он использует уже залогиненную сессию жертвы для выполнения действий. HttpOnly cookies защищают от чтения секретов через JS, но не мешают атакующему делать запросы из страницы.

CSRF (другой сайт инициирует нежелательные действия) в основном угрожает cookie-сессиям, потому что браузер автоматически прикрепляет cookie. Если вы полагаетесь на cookies, нужны явные защиты от CSRF: корректные настройки SameSite, anti-CSRF токены и осторожная обработка запросов, изменяющих состояние. JWT, отправляемые в заголовке Authorization, менее подвержены классическому CSRF, но уязвимы к XSS, если хранятся в месте, доступном JavaScript.

Атаки воспроизведения (replay) — там, где важны серверные сессии: вы можете сразу же аннулировать session ID. Короткоживущие JWT уменьшают окно для replay, но не могут остановить воспроизведение пока токен действителен.

Общие устройства и потерянные телефоны превращают «выйти из системы» в реальную угрозу. Решения обычно сводятся к вопросам: может ли пользователь принудительно выйти с других устройств, как быстро это должно сработать, что происходит при краже refresh-токена и разрешаете ли вы «запомнить меня». Часто для сотрудников применяют более строгие требования, чем для клиентов: короткие таймауты и быстрая отмена доступа.

Cookie-сессии — классика. После входа сервер создаёт запись сессии (обычно ID и поля: user ID, время создания, срок). Браузер хранит только идентификатор сессии в cookie. При каждом запросе браузер отправляет cookie, и сервер по этому ID ищет сессию, чтобы понять кто делает запрос.

Главное преимущество — контроль. Сессию сервер валидирует при каждом запросе. Нужен мгновенный выход — удаляете или отключаете запись на сервере, и сессия перестаёт работать, даже если у пользователя остался cookie.

Большая часть защиты зависит от настроек cookie:

  • HttpOnly: не даёт JavaScript читать cookie.\n- Secure: шлёт cookie только по HTTPS.\n- SameSite: ограничивает отправку cookie при межсайтовых запросах.

Место хранения состояния сессии влияет на масштабируемость. Хранение в памяти приложения просто, но ломается при нескольких серверах или частых перезапусках. База данных даёт долговечность. Redis популярен для быстрых lookup’ов при большом числе активных сессий. Главное — сервер должен уметь находить и валидировать сессию при каждом запросе.

Cookie-сессии подходят, когда нужен строгий выход, например в админ-панелях или клиентских порталах, где админ должен иметь возможность немедленно разлогинить пользователя. Увольнение сотрудника и отключение серверных сессий сразу же закрывает доступ, без ожидания истечения токенов.

JWT access-токены: сильные стороны и острые углы

JWT (JSON Web Token) — подписанная строка с рядом утверждений о пользователе (user ID, роль, tenant) и временем истечения. Ваш API локально проверяет подпись и время, без обращения в базу, и затем принимает решение об авторизации.

Поэтому JWT популярны в API-first продуктах, мобильных приложениях и системах, где несколько сервисов должны валидировать одну и ту же личность. При нескольких бэкенд-инстансах каждый может проверить токен и получить одинаковый ответ.

Достоинства

JWT быстро проверять и удобно передавать между сервисами. Если фронтенд вызывает много эндпоинтов, короткоживущий access-токен упрощает поток: проверить подпись, прочитать user ID и продолжить.

Пример: клиентский портал вызывает «Список счетов» и «Обновить профиль» на разных сервисах. JWT может нести customer ID и роль customer, так что каждый сервис авторизует запрос без обращения к сессии.

Остроты

Главный компромисс — отзыв. Если токен действителен час, он обычно действителен везде в течение этого часа, даже если пользователь нажал «выйти» или админ отключил аккаунт, если вы не добавите дополнительные проверки на сервере.

JWT также утечки в обычных сценариях. Частые точки отказа: localStorage (XSS может прочитать), память браузера (вредоносные расширения), логи и отчёты об ошибках, прокси и аналитика, которые захватывают заголовки, и скопированные токены в чатах поддержки или скриншотах.

Из-за этого JWT access-токены лучше использовать для короткого доступа, а не для «всегда входа». Делайте их минимальными (без чувствительных персональных данных), с коротким сроком и предполагайте, что украденный токен будет действителен до истечения.

Refresh-токены: как сделать JWT работоспособными

Моделируйте сессии и устройства
Определяйте пользователей, устройства и токены в PostgreSQL через AppMaster Data Designer.
Моделировать данные

JWT access-токены предназначены для короткой жизни. Это безопасно, но создаёт практическую проблему: пользователи не должны вводить логин каждые несколько минут. Refresh-токены решают это: приложение тихо получает новый access-токен, когда старый истёк.

Где хранить refresh-токен — ещё важнее, чем где хранить access-токен. В браузерном веб-приложении самым безопасным вариантом по умолчанию будет cookie с флагами HttpOnly и Secure, чтобы JavaScript не мог его прочитать. LocalStorage проще в реализации, но легче украсть при XSS. Если ваша модель угроз включает XSS — не храните долгоживущие секреты в доступном JS хранилище.

Ротация — то, что делает refresh-токены пригодными в реальных системах. Вместо использования одного и того же refresh-токена недели за неделей, вы меняете его при каждом использовании: клиент предъявляет refresh A, сервер выдаёт новый access и refresh B, а refresh A становится недействительным.

Простая схема ротации обычно включает правила:

  • Делайте access-токены короткими (минуты, не часы).\n- Храните refresh-токены на сервере с флагами статуса и временем последнего использования.\n- Ротируйте при каждом обновлении и инвалидируйте предыдущий токен.\n- Привязывайте refresh-токены к устройству/браузеру, когда возможно.\n- Логируйте события обновления для расследования злоупотреблений.

Детекция повторного использования — ключевой сигнал тревоги. Если refresh A уже был обменян, но вы видите его снова, предполагайте, что копия была сделана. Обычная реакция — отозвать всю сессию (и часто все сессии пользователя) и потребовать повторный вход, потому что нельзя понять, какая копия настоящая.

Для выхода серверу нужно понимать отзыв — обычно это таблица сессий (или список отозванных), где помечены refresh-токены как отозванные. Access-токены могут ещё работать до истечения, но вы можете сократить это окно, делая access-токены короткоживущими.

Требования к выходу и что реально выполнимо

«Выход» кажется простым, пока вы его не определите. Обычно есть два запроса: «выйти на этом устройстве» (один браузер или телефон) и «выйти везде» (все активные сессии).

Есть ещё вопрос времени. «Немедленный выход» означает, что приложение перестаёт принимать креденшел прямо сейчас. «Выход по истечении» означает, что приложение перестаёт принимать креденшел, когда сессия или токен естественно истекает.

С cookie-сессиями немедленный выход прост: сервер контролирует сессию. Вы удаляете cookie на клиенте и инвалидируете запись на сервере. Если кто-то ранее скопировал значение cookie, именно отказ сервера обеспечивает выход.

С чисто JWT-аутентификацией (stateless access-токены без серверной проверки) вы не можете гарантировать немедленный выход. Украденный JWT остаётся действительным до истечения, потому что серверу некуда проверить «отозван ли этот токен». Можно добавить denylist, но тогда вы храните состояние и проверяете его — теряется часть простоты.

Практичный паттерн: считаете access-токены короткими и контролируете выход через refresh-токены. Access-токен может «катиться» ещё несколько минут, но refresh-токен — то, что поддерживает сессию. Если украли ноут, отзыв refresh-токена обрывает будущие обновления.

Что вы реально можете обещать:

  • Выйти на этом устройстве: отозвать эту сессию или refresh-токен и удалить локальные cookie/хранилище.\n- Выйти везде: отозвать все сессии или все семейства refresh-токенов для аккаунта.\n- «Немедленный» эффект: гарантирован с серверными сессиями, с access-токенами — лучшая попытка до истечения.\n- Принудительные события выхода: смена пароля, отключение аккаунта, понижение роли.

Для смены пароля и отключения аккаунта не полагайтесь на «пользователь сам выйдет». Храните общесистемную версию сессии (или метку «token valid after»). При каждом обновлении (и иногда при каждом запросе) сверяйте её. Если она изменилась — отклоняйте и требуйте вход заново.

Пошагово: как выбрать подход к сессиям для вашего приложения

Добавьте мобильный вход
Создавайте нативные iOS и Android приложения с одинаковыми правилами аутентификации.
Собрать мобильное

Если хотите оставить дизайн сессий простым, сначала решите правила, а потом — механику. Большинство проблем начинается, когда команды выбирают JWT или cookies потому что они популярны, а не потому что соответствуют рискам и требованиям к выходу.

Начните с перечисления всех клиентов, где пользователь входит. Веб-приложение ведёт себя иначе, чем нативное мобильное, внутренний админ-инструмент или интеграция с партнёром. Каждый случай меняет, где безопасно хранить данные, как обновлять логины и что должно значить «выход».

Практичный порядок действий, который подходит большинству команд:

  1. Перечислите ваши клиенты: web, iOS/Android, внутренние инструменты, доступ для партнёров.\n2. Выберите базовую модель угроз: XSS, CSRF, кража устройства.\n3. Решите, что должен гарантировать выход: это устройство, все устройства, принудительный выход админом.\n4. Выберите базовый паттерн: cookie-сессии (сервер помнит) или access + refresh токены.\n5. Установите таймауты и правила реакции: простое и абсолютное истечение, плюс что делать при подозрительном повторном использовании.

Задокументируйте точные обещания системы. Пример: «Веб-сессии истекают через 30 минут бездействия или через 7 дней максимально. Админ может принудительно разлогинить в течение 60 секунд. Потерянный телефон можно отключить удалённо.» Эти предложения важнее, чем библиотека.

Наконец, добавьте мониторинг, соответствующий выбранному паттерну. Для токенов сильный сигнал — повторное использование refresh-токена. Обращайтесь с ним как с вероятной кражей: отзывайте семейство сессий и уведомляйте пользователя.

Распространённые ошибки, ведущие к компрометации аккаунта

Добавьте логику обнаружения повторного использования
Отслеживайте использование refresh-токенов и реагируйте на повторное использование через drag-and-drop процессы.
Настроить

Большинство захватов аккаунтов — не «умные взломы», а простые ошибки сессий. Хорошая обработка сессий — это в основном отсутствие лёгких путей для воровства или воспроизведения креденшел.

Одна ловушка — хранить access-токены в localStorage и надеяться, что XSS никогда не случится. Если любой скрипт выполнится на странице (плохая зависимость, внедрённый виджет, сохранённый комментарий), он прочитает localStorage и отправит токен наружу. Cookies с флагом HttpOnly уменьшат этот риск, потому что JS не сможет их прочитать.

Другая ошибка — делать JWT долгоживущими, чтобы избежать refresh-токенов. Access-токен на 7 дней даёт окно повторного использования в 7 дней при утечке. Короткий access и управляемый refresh труднее использовать злоумышленнику, особенно если вы можете отключать обновления.

У cookies есть собственный «ствол у ног»: забыть CSRF-защиту. Если ваше приложение использует cookie для аутентификации и вы принимаете запросы, изменяющие состояние, без защиты от CSRF, злой сайт может заставить залогиненный браузер отправить валидный запрос.

Другие проблемы, часто выявляемые после инцидентов:

  • Refresh-токены никогда не ротируются или они ротируются, но вы не детектируете повторное использование.\n- Поддержка разных методов входа (cookie-сессия и bearer-токен) без ясного приоритета на сервере.\n- Токены попадают в логи (консоль браузера, события аналитики, серверные логи) и копируются.

Конкретный пример: агент поддержки вставляет «debug log» в тикет. В логе есть заголовок Authorization. Любой, кто имеет доступ к тикету, может воспроизвести этот токен и действовать от имени агента. Обращайтесь с токенами как с паролями: не печатайте, не храните и делайте их короткоживущими.

Быстрая проверка перед релизом

Большинство багов с сессиями не про сложную криптографию, а про один пропущенный флаг, один слишком долгоживущий токен или один endpoint, который должен был требовать повторной аутентификации.

Перед релизом пройдитесь простым чек-листом: что может сделать атакующий со украденным cookie или токеном. Это один из быстрых способов повысить безопасность без полной переработки auth-схемы.

Чек-лист перед релизом

Пройдитесь по этим пунктам в staging и снова в production:

  • Держите access-токены короткими (минуты) и проверьте, что API действительно отклоняет их по истечении.\n- Обращайтесь с refresh-токенами как с паролями: храните их так, чтобы JS не мог прочитать, отправляйте только на endpoint обновления и ротируйте при каждом использовании.\n- Если используете cookies для auth, проверьте флаги: HttpOnly включён, Secure включён, SameSite настроен намеренно. Проверьте область cookie (domain и path) — не шире, чем нужно.\n- Если cookies аутентифицируют запросы, добавьте защиту от CSRF и убедитесь, что state-changing эндпоинты отказываются без CSRF-сигнала.\n- Сделайте отзыв реальным: после сброса пароля или отключения аккаунта существующие сессии должны быстро перестать работать (удаление серверных сессий, инвалидизация refresh-токенов или проверка «версии сессии»).

После этого протестируйте обещания о выходе. «Выйти» часто означает «удалить локальную сессию», но пользователи ожидают большего.

Практический тест: залогиньтесь на ноутбуке и телефоне, затем смените пароль. Ноутбук должен быть вынужден выйти при следующем запросе, а не через несколько часов. Если вы предлагаете «выйти везде» и список устройств, проверьте, что каждое устройство сопоставлено с отдельной сессией или записью refresh-токена, которую можно отозвать.

Пример: клиентский портал с учётными записями сотрудников и принудительным выходом

Деплой там, где работает команда
Разворачивайте в AppMaster Cloud, AWS, Azure, Google Cloud или экспортируйте исходники.
Деплоить

Представьте небольшую компанию с веб-порталом для клиентов (проверка счетов, тикетов) и мобильным приложением для сотрудников (задания, заметки, фото). Сотрудники иногда работают в подвалах без связи, поэтому приложение должно некоторое время работать офлайн. Админы хотят большую красную кнопку: если планшет потерян или подрядчик уволен, они могут принудительно разлогинить.

Добавьте три угрозы: общие планшеты в фургонах (кто-то забыл выйти), фишинг (сотрудник ввёл данные на фейковой странице) и периодическая XSS-ошибка в портале (скрипт пытается украсть всё, что может).

Практичная схема тут — короткоживущие access-токены плюс ротирующие refresh-токены с серверной возможностью отзыва. Это даёт быстрые API-вызовы и офлайн-работу, одновременно позволяя админам отрезать сессии.

Возможная конфигурация:

  • Время жизни access-токена: 5–15 минут.\n- Ротация refresh-токенов: при каждом обновлении возвращается новый refresh-токен, старый становится недействительным.\n- Безопасное хранение refresh-токенов: в web — HttpOnly Secure cookie; в мобильном — защищённое хранилище ОС.\n- Отслеживание refresh-токенов на сервере: запись токена (пользователь, устройство, время выдачи, время последнего использования, флаг отзыва). При повторном использовании ротированного токена — считать это кражей и отзывать всю цепочку.

Принудительный выход делается выполнимым: админ отзывает запись refresh-токена для конкретного устройства (или все устройства пользователя). Украденное устройство может ещё использовать текущий access-токен до его истечения, но получить новый не сможет. Максимальное время полного отрезания доступа — это время жизни access-токена.

Для потерянного устройства чётко пропишите правило: «В течение 10 минут приложение перестанет синхронизироваться и потребует вход.» Офлайн-работа может оставаться на устройстве, но следующая онлайн-синхронизация должна провалиться до повторного входа.

Следующие шаги: реализовать, протестировать и упростить поддержку

Пропишите, что значит «выйти» простым языком. Например: «Выход удаляет доступ на этом устройстве», «Выход везде отключает все устройства в течение 1 минуты», «Смена пароля разлогинивает другие сессии». Эти обещания определяют, нужна ли серверная сессия, списки отзыва или короткие токены.

Переведите обещания в небольшой тест-план. Баги с токенами обычно выглядят нормально в счастливых сценариях, но проваливаются в реальной жизни (режим сна, плохая сеть, несколько устройств).

Практический тестовый чек-лист

Запустите тесты для непредсказуемых сценариев:

  • Истечение: доступ прекращается, когда access-токен или сессия истекает, даже если браузер открыт.\n- Отзыв: после «выйти везде» старые креденшелы не проходят при следующем запросе.\n- Ротация: при обновлении возвращается новый refresh-токен, старый инвалидируется.\n- Обнаружение повторного использования: повторное воспроизведение старого refresh-токена вызывает блокировку.\n- Мультиустройство: правила для «только текущее устройство» vs «все устройства» выполняются и UI соответствует.

После тестов проведите имитацию атак с командой. Пройдите три сценария: XSS, который может читать токены; CSRF против cookie-сессий; и потерянный телефон с активной сессией. Проверяйте, совпадает ли дизайн с обещаниями.

Если нужно действовать быстро, уменьшите количество кастомной связки. AppMaster (appmaster.io) — один из вариантов, если вам нужен сгенерированный, готовый к продакшну бэкенд плюс веб и нативные мобильные приложения, чтобы держать правила как истечения, ротации и принудительного выхода согласованными на всех клиентах.

Назначьте повторный обзор после запуска. Используйте реальные тикеты поддержки и инциденты, чтобы корректировать таймауты, лимиты сессий и поведение «выйти везде», затем снова прогоняйте чек-лист, чтобы исправления не регрессировали.

Легко начать
Создай что-то невероятное

Экспериментируйте с AppMaster с бесплатной подпиской.
Как только вы будете готовы, вы сможете выбрать подходящий платный план.

Попробовать AppMaster