Access-токены и HttpOnly-cookie: безопасная авторизация Go и Vue
📋 Кратко
Безопасная авторизация не сводится к добавлению флага HttpOnly. В статье разбираем связку Go и Vue, где короткоживущий access-токен хранится в памяти приложения, а refresh-токен передаётся в защищённой cookie. Объясняем различия между токенами и сессиями, настройку Secure и SameSite, защиту от CSRF и XSS, проверку JWT, обновление сессии и отзыв токенов.
Авторизация в веб-приложении начинается не с выбора библиотеки. Сначала нужно решить, где живут учетные данные. От этого зависят последствия XSS, CSRF, утечки журналов и перехвата запросов.
Практичная схема для Go и Vue разделяет роли токенов. Короткоживущий access-токен хранится в памяти приложения. Долгоживущий refresh-токен находится в HttpOnly-cookie. Такой подход снижает ценность утечки access-токена и не даёт JavaScript прочитать refresh-токен.
🔍 Зачем разделять access- и refresh-токены
Access-токен подтверждает доступ к API. Клиент передаёт его при каждом запросе к защищённому ресурсу. Его срок жизни обычно составляет 5–15 минут.
Refresh-токен нужен для обновления access-токена. Клиент отправляет его только на специальный адрес обновления. Такой токен живёт дни или месяцы, поэтому требует более строгой защиты.
Разделение уменьшает последствия кражи access-токена. Если злоумышленник получает его, срок действия быстро заканчивается. Refresh-токен при этом остаётся недоступным для обычного JavaScript.
Чем схема отличается от серверной сессии
Серверная сессия хранит состояние на сервере. Браузер получает идентификатор сессии в cookie. Сервер связывает этот идентификатор с пользователем и правами.
Access-токен обычно несёт claims (утверждения) внутри самого токена. Сервер проверяет подпись и ограничения. Но подпись не превращает содержимое токена в секрет.
Что сделать сейчас: отдельно опишите срок жизни, место хранения и назначение каждого токена. Не используйте одну строку для access и refresh.
🧩 Где хранить токены в приложении Vue
Постоянное хранилище браузера удобно, но увеличивает последствия XSS. Токен из localStorage или sessionStorage может прочитать вредоносный скрипт. Поэтому хранить там долгоживший refresh-токен особенно рискованно.
Access-токен лучше держать в памяти приложения. После перезагрузки страницы он исчезает. Vue восстанавливает авторизацию через запрос обновления, а сервер возвращает новый access-токен.
В памяти токен можно хранить в состоянии приложения. Подойдут отдельный модуль, хранилище состояния или простой объект. Важно не выводить значение в консоль и не помещать его в текст ошибок.
Как клиент восстанавливает сессию
При запуске Vue отправляет запрос на endpoint обновления. Браузер сам прикладывает cookie, если политика cookie и настройки запроса это разрешают.
Сервер проверяет refresh-токен. При успехе он выдаёт новый access-токен. При ошибке клиент очищает состояние и показывает экран входа.
Запросы API передают access-токен через заголовок Authorization. Не добавляйте токен в адрес страницы, параметры запроса или фрагмент URL.
Что сделать сейчас: проверьте проект поиском по localStorage, sessionStorage, URL и console.log. Удалите из этих мест access- и refresh-токены.
🍪 Как настроить HttpOnly-cookie на сервере Go
HttpOnly запрещает JavaScript читать значение cookie через стандартный интерфейс браузера. Это снижает риск прямой кражи refresh-токена при чтении cookie скриптом.
Флаг Secure разрешает отправлять cookie только по HTTPS. Без него токен может уйти по небезопасному соединению. В рабочей среде приложение должно использовать защищённый транспорт.
SameSite ограничивает отправку cookie в межсайтовых сценариях. Этот атрибут помогает снизить риск CSRF. Его значение выбирают с учётом архитектуры и доменов приложения.
Пример безопасных параметров
В Go параметры cookie задаются через http.Cookie. Ограничьте Path адресом обновления, если refresh-токен нужен только там. Не расширяйте Domain без понятной причины.
http.SetCookie(w, &http.Cookie{
Name: "refresh_token",
Value: refreshToken,
Path: "/auth/refresh",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
Пример показывает принцип настройки, а не готовую схему для копирования без проверки. Значение токена нельзя записывать в журналы. Ошибки также не должны содержать cookie или заголовки авторизации.
Что сделать сейчас: включите Secure и HttpOnly, задайте узкий Path, проверьте SameSite и исключите секреты из журналов.
🛡️ Почему HttpOnly не закрывает XSS и CSRF
XSS позволяет выполнить чужой код внутри страницы. Скрипт не прочитает HttpOnly-cookie, но сможет отправить запрос от имени пользователя. Браузер приложит cookie автоматически.
Такой сценарий особенно опасен для операций изменения данных. Злоумышленник может попытаться вызвать перевод, сменить настройки или создать новую сессию. Защита должна учитывать не только кражу значения, но и отправку действий.
CSRF возникает, когда сторонний сайт заставляет браузер отправить запрос к вашему приложению. SameSite снижает риск, но не заменяет проверку на сервере. Для важных действий применяйте CSRF-токен.
Проверки для cookie-запросов
Сервер может проверять Origin и Referer. Эти заголовки помогают понять источник запроса. При отсутствии ожидаемого источника запрос нужно отклонять для чувствительных операций.
CSRF-токен должен быть непредсказуемым и связанным с пользовательской сессией. Сервер сравнивает его с ожидаемым значением. Один только заголовок X-Requested-With не заменяет полноценную проверку.
Для форм и запросов изменения используйте отдельную защиту. Запрос обновления токена также должен иметь понятную политику источников. Не делайте endpoint обновления без ограничений.
Что сделать сейчас: составьте список операций изменения данных. Для каждой включите проверку Origin, подходящий SameSite и CSRF-токен там, где он нужен.
⚙️ Как сервер Go проверяет access-токен
JWT состоит из трёх частей: header, payload и signature. Первые части кодируются в Base64Url. Это кодировка, а не шифрование.
Payload может прочитать любой обладатель строки токена. Поэтому туда нельзя помещать пароль, секретный ключ или другие тайные данные. Claims должны содержать только необходимые сведения.
Подпись подтверждает целостность токена. В описанной схеме применяется HMAC SHA-256. Сервер пересчитывает подпись и сравнивает результат безопасным способом.
Что проверять после подписи
Валидная подпись не означает, что токен можно принимать без ограничений. Сервер отдельно проверяет срок действия. Также нужны проверки типа токена, издателя и получателя, если приложение использует эти поля.
Проверяйте алгоритм из заранее разрешённого списка. Не принимайте любой алгоритм из заголовка. Не смешивайте access- и refresh-токены в одном обработчике.
Проверяйте expiration и not-before. Первый параметр ограничивает срок жизни. Второй не позволяет использовать токен до разрешённого момента.
Что сделать сейчас: вынесите проверку JWT в единый middleware Go. Запретите неизвестные алгоритмы и добавьте отдельные проверки срока и назначения.
🔐 Как обновлять токены без гонок запросов
Access-токен истекает во время работы приложения. Vue получает ошибку авторизации и запускает обновление. После успешного ответа клиент повторяет исходный запрос.
Несколько запросов могут завершиться одновременно. Если каждый запустит собственное обновление, сервер получит лишнюю нагрузку. Клиент может также запутаться в последовательности новых токенов.
Централизованный обработчик Vue
Axios использует единый обработчик ответов. Он запускает только один запрос обновления. Остальные запросы ждут его результат.
После успеха обработчик заменяет access-токен в памяти. Затем он повторяет отложенные запросы. После ошибки клиент завершает локальную сессию.
Запрос обновления не должен сам запускать новый цикл обновления. Иначе ошибка сервера создаёт бесконечную цепочку. Для него нужен отдельный маршрут и отдельные правила обработки.
После перезагрузки страницы память очищается. Клиент снова вызывает обновление и получает новый access-токен. Такой механизм сохраняет удобство без постоянного хранения access-токена.
Что сделать сейчас: добавьте единую очередь обновления. Проверьте сценарии параллельных запросов, истёкшего токена и недоступного сервера.
📋 Как отзывать refresh-токены и завершать сессии
Срок действия не решает все задачи. Пользователь может потерять устройство. Администратор может заблокировать учётную запись. Серверу нужен способ немедленно прекратить сессию.
Refresh-токен стоит связывать с записью сессии на сервере. Запись может хранить пользователя, устройство, дату создания и статус. Сам токен не должен появляться в открытом виде в журналах.
При обновлении применяйте ротацию. Старый refresh-токен становится недействительным, а сервер выдаёт новый. Повторное использование старого значения считается подозрительным событием.
Выход с одного и всех устройств
Обычный выход отзывает текущую сессию. Сервер удаляет или блокирует её запись. Браузер получает команду удалить cookie.
Выход со всех устройств отзывает все активные сессии пользователя. Access-токены могут оставаться действительными до окончания короткого срока. Поэтому критичные операции требуют дополнительной проверки статуса пользователя.
Идентификатор токена помогает найти конкретную сессию. Он также помогает обнаружить повторное использование. Система должна фиксировать событие без записи самого секрета.
Что сделать сейчас: реализуйте отзыв текущей сессии и всех сессий. Добавьте событие повторного использования refresh-токена в мониторинг.
🌐 Как настроить CORS и доверие между Go и Vue
Vue и API могут работать на разных источниках. Тогда браузер применяет правила CORS. Сервер должен явно указать разрешённый источник.
Нельзя сочетать wildcard-источник и передачу учетных данных. При credentials сервер указывает конкретный Origin. Это снижает риск случайного доверия к чужому сайту.
Разрешайте только нужные методы и заголовки. В список заголовков добавляйте Authorization, если клиент передаёт access-токен так. Не разрешайте лишние источники ради временного удобства.
Проверка настроек
Проверьте основной домен, адрес API и среду разработки отдельно. Разные источники не должны автоматически получать одинаковые права.
Проверьте предварительные OPTIONS-запросы. Сервер должен корректно отвечать на них, но не должен превращать такую проверку в обход авторизации.
Cookie и заголовок Authorization требуют разных тестов. Убедитесь, что браузер не отправляет cookie на неожиданный источник. Также проверьте отказ при неправильном Origin.
Что сделать сейчас: замените wildcard на список разрешённых источников. Отдельно протестируйте запросы с credentials и без них.
🚀 Чек-лист внедрения для Go и Vue
Безопасная авторизация состоит из нескольких небольших решений. Ошибка в одном месте может свести пользу остальных настроек к нулю.
- Храните access-токен только в памяти приложения Vue.
- Храните refresh-токен в cookie с HttpOnly и Secure.
- Задавайте подходящий SameSite и узкий Path.
- Передавайте access-токен через Authorization, а не через URL.
- Используйте короткий срок access-токена в пределах принятой схемы.
- Проверяйте подпись, алгоритм, срок, issuer, audience и not-before.
- Не помещайте пароли и секреты в payload JWT.
- Защищайте cookie-запросы от CSRF.
- Включайте HTTPS для всех рабочих запросов.
- Не записывайте токены в журналы, трассировки и сообщения ошибок.
- Защищайте endpoint обновления от повторного использования токенов.
- Поддерживайте выход с одного и всех устройств.
- Ограничивайте CORS конкретными источниками.
- Не запускайте несколько обновлений access-токена одновременно.
JWT удобен, но не является обязательным выбором. Серверная сессия может быть проще для приложения с одним backend. Гибридная схема полезна, когда клиенту нужен короткий access-токен, а refresh нужно скрыть от JavaScript.
Главный риск появляется не из-за конкретного фреймворка. Его создаёт неверная граница доверия между браузером, API и хранилищем токенов.
Что сделать сейчас: превратите список в задачи команды. Закройте сначала хранение токенов, HTTPS, CSRF, CORS и отзыв сессий.
📚 Читайте также
- Инцидент OpenAI и Hugging Face: чему учат новые утечки данных
- DNS Rebinding: как закрыть доступ браузера к локальным сервисам
- MFA и криптография: почему OTP уступает WebAuthn и FIDO2
- 737 VPN-расширений Chrome: расследуем скрытую прокси-атаку
- Телеметрия умного дома без утечек: DNS и локальные сервисы
📖 Термины
CSRF · IAM (Identity and Access Management) · OAuth · Приватность · Шифрование