Вход и сессии: где ошибается авторизация, написанная ИИ
Токен в localStorage или в cookie, сессии без срока, дырявый сброс пароля и проверки, которые живут только в интерфейсе.
Форма входа — первое, что ассистент собирает в любом приложении, и выглядит она всегда одинаково убедительно. Поле почты, поле пароля, кнопка, редирект в кабинет. Ошибки начинаются за этой формой: где хранится токен, сколько он живёт, что происходит при сбросе пароля и кто на сервере проверяет, что человек вообще имеет право на то, что просит.
Токен в localStorage или в cookie
Ассистенты почти всегда кладут токен в localStorage: это проще всего и работает сразу. Цена — любой скрипт на странице может его прочитать. Одна XSS-уязвимость, одна скомпрометированная библиотека аналитики — и токен уезжает к чужому человеку, который пользуется им со своего компьютера.
Cookie с флагами HttpOnly, Secure и SameSite=Lax недоступна из JavaScript, и украсть её через XSS нельзя. Зато появляется другая забота — CSRF: браузер отправляет cookie сам, и запросы, меняющие данные, нужно от этого защищать. SameSite закрывает большую часть случаев, для остального есть CSRF-токен.
Однозначного «правильно» нет, но для обычного веб-приложения с собственным бэкендом cookie обычно безопаснее. Если остаёшься на localStorage, хотя бы сделай токен короткоживущим и серьёзно отнесись к XSS.
Сессии без срока
Частая находка: JWT без поля exp или со сроком в год. Такой токен — бессрочный пропуск. Выход из аккаунта удаляет его из браузера, но сервер продолжает его принимать, если у кого-то осталась копия.
Что нужно:
- Короткий токен доступа (минуты или часы) и отдельный токен обновления, который можно отозвать.
- Хранилище сессий на сервере или список отозванных токенов — чтобы «выйти» и «выйти на всех устройствах» действительно что-то делали.
- Отзыв всех сессий при смене пароля и почты.
Сброс пароля
Здесь ошибки самые дорогие, потому что сброс — это вход без пароля по определению.
- Ссылка не одноразовая и без срока. Письмо полугодовой давности в чужом ящике должно быть бесполезным. Срок — минуты или часы, использование — одно.
- Токен угадывается. Идентификатор пользователя, время или короткое число вместо криптографически случайной строки.
- Ссылка собирается из заголовка
Host. Тогда злоумышленник запрашивает сброс для чужой почты с подменённым заголовком, и жертва получает настоящее письмо со ссылкой на чужой домен. Адрес сайта берётся из конфига, а не из запроса. - Ответ выдаёт, есть ли такая почта. «Пользователь не найден» превращает форму в инструмент проверки чужих адресов. Отвечай одинаково: «если такой адрес есть, мы отправили письмо».
- После сброса старые сессии живы. Человек сбросил пароль, потому что подозревает взлом, а тот, кто взломал, остаётся внутри.
Проверки только в интерфейсе
Самая распространённая и самая опасная ошибка — права проверяет интерфейс. Кнопка «Админка» скрыта, если в профиле нет флага isAdmin, а сам флаг лежит в localStorage или приходит с клиента в теле запроса.
Признаки:
- Роль или тариф читаются из того, что прислал браузер.
- Middleware защищает страницы, но не API: в Next.js легко настроить
matcherтак, что/apiпод него не попадает. - Сервер проверяет, что человек вошёл, но не проверяет, что запись принадлежит ему.
Правило простое: каждый серверный обработчик сам выясняет, кто пришёл (по сессии, а не по полю в запросе), и сам решает, можно ли ему это. Интерфейс только прячет кнопки, чтобы не путать людей.
Мелочи, которые потом не мелочи
Смена почты без повторного ввода пароля. Привязка входа через соцсеть к существующему аккаунту по неподтверждённому адресу. Подтверждение почты, которое можно пропустить, просто перейдя на другую страницу. Каждая из этих вещей занимает строчку кода и открывает чужой аккаунт.
Если авторизацию в твоём продукте писал ассистент и никто её с тех пор не читал, начни с неё. SUBXCAT проверяет такие места и чинит их без переписывания всего входа.